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"
2025-02-11 14:49:16 +08:00
models2 "go-admin/models"
2025-02-06 11:14:33 +08:00
"go-admin/pkg/utility"
2025-02-10 18:21:44 +08:00
"go-admin/pkg/utility/snowflakehelper"
2025-02-06 11:14:33 +08:00
"strconv"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
2025-02-10 18:21:44 +08:00
"github.com/jinzhu/copier"
2025-02-06 11:14:33 +08:00
"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-11 18:03:30 +08:00
if originOrderSn != nil && originOrderSn != "" {
2025-02-08 14:05:57 +08:00
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 )
2025-02-10 18:21:44 +08:00
//主单减仓完毕
2025-02-14 09:43:49 +08:00
case preOrder . OrderType == 4 && orderStatus == 6 :
2025-02-10 18:21:44 +08:00
handleMainReduceFilled ( db , preOrder )
2025-02-06 11:14:33 +08:00
//主单取消
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
//主单止损回调
2025-02-10 18:21:44 +08:00
case preOrder . OrderType == 2 && orderStatus == 6 :
removeSpotLossAndAddPosition ( preOrder )
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id =?" , preOrder . MainId ) . Update ( "status" , 9 ) . Error ; err != nil {
logger . Errorf ( "主单止损回调 订单号:%s 修改主单状态失败:%v" , preOrder . OrderSn , err )
}
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "main_id =? AND status =0" , preOrder . MainId ) . Update ( "status" , 4 ) . Error ; err != nil {
logger . Errorf ( "主单止损回调 订单号:%s 修改主单状态失败:%v" , preOrder . OrderSn , err )
}
}
}
// 主单减仓完毕
func handleMainReduceFilled ( db * gorm . DB , preOrder * DbModels . LinePreOrder ) {
apiUserInfo , _ := GetApiInfo ( preOrder . ApiId )
2025-02-14 09:43:49 +08:00
mainId := preOrder . Id
if preOrder . MainId > 0 {
mainId = preOrder . MainId
}
2025-02-10 18:21:44 +08:00
if apiUserInfo . Id == 0 {
logger . Errorf ( "handleMainReduceFilled 获取api信息失败,订单号:%s" , preOrder . OrderSn )
return
}
tradeSet , err := GetTradeSet ( preOrder . Symbol , 0 )
if err != nil {
logger . Errorf ( "handleMainReduceFilled 获取交易对设置失败,订单号:%s" , preOrder . OrderSn )
return
}
price := utility . StrToDecimal ( preOrder . Price )
2025-02-11 14:49:16 +08:00
parentOrder , _ := GetOrderById ( db , preOrder . Pid )
2025-02-10 18:21:44 +08:00
orders := make ( [ ] models . LinePreOrder , 0 )
rate := utility . StringAsFloat ( preOrder . Rate )
ext := models . LinePreOrderExt { }
//获取订单配置
db . Model ( & ext ) . Where ( "order_id =?" , preOrder . Pid ) . First ( & ext )
2025-02-14 09:43:49 +08:00
// 100%减仓 终止流程
if rate >= 100 {
removeSpotLossAndAddPosition ( preOrder )
ids := [ ] int { preOrder . MainId , preOrder . Pid }
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id IN ? AND status =6" , ids ) . Update ( "status" , 9 ) . Error ; err != nil {
logger . Info ( "100%减仓完毕,终结流程" )
2025-02-10 18:21:44 +08:00
}
2025-02-14 09:43:49 +08:00
return
}
totalLossAmountU , _ := GetTotalLossAmount ( db , mainId )
totalNum := getSpotPositionAvailableQuantity ( db , apiUserInfo , preOrder , tradeSet ) //getSpotTotalNum(apiUserInfo, preOrder, tradeSet)
takeProfitOrder := models . LinePreOrder { }
copier . Copy ( & takeProfitOrder , & preOrder )
takeProfitOrder . Id = 0
takeProfitOrder . Pid = preOrder . Id
takeProfitOrder . OrderSn = utility . Int64ToString ( snowflakehelper . GetOrderId ( ) )
takeProfitOrder . Status = 0
// takeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(ext.TakeProfitRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
takeProfitOrder . OrderType = 1
takeProfitOrder . Rate = "100"
takeProfitOrder . SignPrice = preOrder . Price
takeProfitOrder . CreatedAt = time . Now ( )
takeProfitOrder . BuyPrice = "0"
takeProfitOrder . MainOrderType = "LIMIT"
takeProfitOrder . Num = totalNum . String ( )
takeProfitOrder . Rate = ext . ReduceTakeProfitRatio . String ( )
//止盈需要累加之前的亏损
if totalLossAmountU . Cmp ( decimal . Zero ) > 0 {
2025-02-15 18:38:58 +08:00
percent := totalLossAmountU . Div ( totalNum ) . Div ( price ) . Abs ( )
2025-02-14 09:43:49 +08:00
takeProfitOrder . Rate = percent . Mul ( decimal . NewFromInt ( 100 ) ) . Add ( ext . ReduceTakeProfitRatio ) . Truncate ( 2 ) . String ( )
}
setPrice ( & takeProfitOrder , preOrder , tradeSet )
orders = append ( orders , takeProfitOrder )
//有止损单
if ext . ReduceStopLossRatio . Cmp ( decimal . Zero ) > 0 {
var stoploss models . LinePreOrder
copier . Copy ( & stoploss , & preOrder )
stoploss . Id = 0
stoploss . Pid = preOrder . Id
stoploss . OrderSn = utility . Int64ToString ( snowflakehelper . GetOrderId ( ) )
stoploss . Status = 0
stoploss . CreatedAt = time . Now ( )
stoploss . OrderType = 2
stoploss . SignPrice = preOrder . Price
stoploss . BuyPrice = "0"
stoploss . Rate = ext . ReduceStopLossRatio . String ( )
stoploss . MainOrderType = "LIMIT"
stoploss . Num = totalNum . String ( )
// stoploss.Price = price.Mul(decimal.NewFromInt(1).Sub(ext.ReduceStopLossRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
setPrice ( & stoploss , preOrder , tradeSet )
orders = append ( orders , stoploss )
}
if err := db . Create ( & orders ) . Error ; err != nil {
logger . Errorf ( "handleMainReduceFilled 创建止盈止损单失败:%v" , err )
return
}
2025-02-10 18:21:44 +08:00
2025-02-14 09:43:49 +08:00
spotApi := SpotRestApi { }
paramsMap := OrderPlacementService {
ApiId : takeProfitOrder . ApiId ,
Symbol : takeProfitOrder . Symbol ,
Side : takeProfitOrder . Site ,
Type : "LIMIT" ,
TimeInForce : "GTC" ,
Price : utility . StrToDecimal ( takeProfitOrder . Price ) ,
Quantity : totalNum ,
NewClientOrderId : takeProfitOrder . OrderSn ,
StopPrice : utility . StrToDecimal ( takeProfitOrder . Price ) ,
}
if err := spotApi . OrderPlaceLoop ( db , paramsMap , 3 ) ; err != nil {
logger . Errorf ( "减仓后重下止盈失败 减仓order_sn:%s err:%v" , preOrder . OrderSn , err )
2025-02-10 18:21:44 +08:00
2025-02-14 09:43:49 +08:00
if err2 := db . Model ( & takeProfitOrder ) . Updates ( map [ string ] interface { } { "status" : 2 , "" : err . Error ( ) } ) . Error ; err2 != nil {
logger . Errorf ( "handleMainReduceFilled 更新止盈单失败:%v" , err2 )
2025-02-06 18:03:09 +08:00
}
2025-02-06 11:14:33 +08:00
}
2025-02-10 18:21:44 +08:00
2025-02-15 18:38:58 +08:00
for _ , item := range orders {
if item . OrderType == 2 {
processStopLossOrder ( item )
}
}
2025-02-11 14:49:16 +08:00
//计算实际亏损
if parentOrder . Price != "" {
parentPrice := utility . StrToDecimal ( parentOrder . Price )
reduceNum := utility . StrToDecimal ( preOrder . Num )
2025-02-14 09:43:49 +08:00
lossAmountU := price . Sub ( parentPrice ) . Abs ( ) . Mul ( reduceNum ) //.Truncate(int32(tradeSet.PriceDigit))
2025-02-11 14:49:16 +08:00
if err := db . Model ( & parentOrder ) . Update ( "loss_amount" , lossAmountU ) . Error ; err != nil {
logger . Errorf ( "修改主单实际亏损失败 订单号:%s err:%v" , parentOrder . OrderSn , err )
}
}
2025-02-10 18:21:44 +08:00
//加仓待触发
addPositionOrder := DbModels . LinePreOrder { }
2025-02-14 09:43:49 +08:00
if err := db . Model ( & addPositionOrder ) . Where ( "main_id =? AND order_category=3 AND order_type=0 AND status=0" , preOrder . MainId ) . First ( & addPositionOrder ) . Error ; err != nil {
2025-02-10 18:21:44 +08:00
logger . Errorf ( "handleMainReduceFilled 获取加仓单失败,订单号:%s err:%v" , preOrder . OrderSn , err )
return
}
keySpotAddPosition := fmt . Sprintf ( rediskey . SpotAddPositionList , global . EXCHANGE_BINANCE )
addPositionData := AddPositionList {
2025-02-14 09:43:49 +08:00
Id : addPositionOrder . Id ,
OrderSn : addPositionOrder . OrderSn ,
2025-02-10 18:21:44 +08:00
MainId : addPositionOrder . MainId ,
Pid : addPositionOrder . Pid ,
Price : utility . StrToDecimal ( addPositionOrder . Price ) ,
ApiId : addPositionOrder . ApiId ,
Symbol : addPositionOrder . Symbol ,
Side : addPositionOrder . Site ,
SymbolType : addPositionOrder . SymbolType ,
}
addVal , err := sonic . MarshalString ( addPositionData )
if err != nil {
logger . Errorf ( "handleMainReduceFilled 序列化加仓单失败,订单号:%s err:%v" , preOrder . OrderSn , err )
return
}
if err := helper . DefaultRedis . RPushList ( keySpotAddPosition , addVal ) ; err != nil {
logger . Errorf ( "handleMainReduceFilled 添加加仓单失败,订单号:%s err:%v" , preOrder . OrderSn , err )
}
2025-02-06 11:14:33 +08:00
}
2025-02-14 09:43:49 +08:00
// 获取主单可用数量
func getSpotPositionAvailableQuantity ( db * gorm . DB , apiUserInfo DbModels . LineApiUser , preOrder * DbModels . LinePreOrder , tradeSet models2 . TradeSet ) decimal . Decimal {
totalNum := getSpotTotalNum ( apiUserInfo , preOrder , tradeSet )
//biance查询失败 查系统内数量
if totalNum . Cmp ( decimal . Zero ) <= 0 {
var mainId int
if preOrder . MainId > 0 {
mainId = preOrder . MainId
} else {
mainId = preOrder . Id
}
totalNum = getInternalNum ( db , mainId )
}
return totalNum
}
// 获取现货剩余数量
func getSpotTotalNum ( apiUserInfo DbModels . LineApiUser , preOrder * DbModels . LinePreOrder , tradeSet models2 . TradeSet ) decimal . Decimal {
var totalNum decimal . Decimal
var err error
for x := 1 ; x < 4 ; x ++ {
totalNum , err = getSpotPositionNum ( apiUserInfo , preOrder , tradeSet )
if err == nil {
break
}
time . Sleep ( time . Millisecond * 150 )
}
if err != nil {
totalNum = utility . StrToDecimal ( preOrder . Num )
}
return totalNum
}
// 获取系统内剩余数量
func getInternalNum ( db * gorm . DB , mainId int ) decimal . Decimal {
var totalNum , totalSellNum decimal . Decimal
var data DbModels . LinePreOrder
if err := db . Model ( & data ) . Where ( "(main_id =? OR id =?) AND order_type =0 AND status =6" , mainId , mainId ) . Select ( "COALESCE(sum(num), 0)" ) . Find ( & totalNum ) . Error ; err != nil {
logger . Errorf ( "getSpotInternalNum 查询所有数量失败,mainId:%d err:%v" , mainId , err )
}
if err := db . Model ( & data ) . Where ( "main_id =? AND order_type =4 AND status =6" , mainId ) . Select ( "COALESCE(sum(num), 0)" ) . Find ( & totalSellNum ) . Error ; err != nil {
logger . Errorf ( "getSpotInternalNum 查询所有减仓数量失败,mainId:%d err:%v" , mainId , err )
}
return totalNum . Sub ( totalSellNum ) . Abs ( )
}
2025-02-11 14:49:16 +08:00
// 获取现货持仓数量
func getSpotPositionNum ( apiUserInfo DbModels . LineApiUser , preOrder * DbModels . LinePreOrder , tradeSet models2 . TradeSet ) ( decimal . Decimal , error ) {
client := GetClient ( & apiUserInfo )
resp , _ , err := client . SendSpotAuth ( "/api/v3/account" , "GET" , map [ string ] interface { } {
"omitZeroBalances" : true ,
} )
if err != nil {
logger . Errorf ( "api_id:%d 交易对:%s 查询用户信息失败:%v" , apiUserInfo . Id , preOrder . Symbol , err )
return decimal . Decimal { } , err
}
var balanceInfo SpotAccountInfo
sonic . Unmarshal ( resp , & balanceInfo )
suffix := utility . ReplaceSuffix ( preOrder . Symbol , preOrder . QuoteSymbol , "" )
var num decimal . Decimal
for _ , item := range balanceInfo . Balances {
if item . Asset == suffix {
if utility . StrToDecimal ( item . Free ) . Cmp ( decimal . Zero ) <= 0 {
logger . Error ( "handleMainReduceFilled 账户余额不足,交易对:%s 订单号:%s" , preOrder . Symbol , preOrder . OrderSn )
return decimal . Decimal { } , errors . New ( "账户余额不足" )
}
num = utility . StrToDecimal ( item . Free ) . Mul ( decimal . NewFromFloat ( 0.998 ) ) . Truncate ( int32 ( tradeSet . AmountDigit ) )
break
}
}
return num , nil
}
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 {
2025-02-10 18:21:44 +08:00
ids := [ ] int { preOrder . Pid , preOrder . MainId }
db . Transaction ( func ( tx * gorm . DB ) error {
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id IN ? AND status=6" , ids ) . Update ( "status" , 9 ) . Error ; err != nil {
logger . Errorf ( "平仓订单回调失败, 回调订单号:%s 更新主单失败:%v" , preOrder . OrderSn , err )
return err
}
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "main_id =? AND status=0" , preOrder . MainId ) . Update ( "status" , 4 ) . Error ; err != nil {
logger . Errorf ( "平仓订单回调失败, 回调订单号:%s 更新子单失败:%v" , preOrder . OrderSn , err )
return err
}
return nil
} )
2025-02-06 18:03:09 +08:00
}
2025-02-10 18:21:44 +08:00
removeSpotLossAndAddPosition ( preOrder )
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-10 18:21:44 +08:00
removeSpotLossAndAddPosition ( preOrder )
db . Transaction ( func ( tx * gorm . DB ) error {
ids := [ ] int { preOrder . Pid , preOrder . MainId }
2025-02-14 10:54:30 +08:00
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id IN ? AND status =6 AND order_type=0" , ids ) . Update ( "status" , 9 ) . Error ; err != nil {
2025-02-10 18:21:44 +08:00
logger . Errorf ( "止盈订单回调失败, 回调订单号:%s 更新主单失败:%v" , preOrder . OrderSn , err )
return err
}
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "main_id =? AND status=0" ) . Update ( "status" , 4 ) . Error ; err != nil {
logger . Errorf ( "止盈订单回调失败, 回调订单号:%s 更新取消状态失败:%v" , preOrder . OrderSn , err )
return err
}
return nil
} )
2025-02-06 18:03:09 +08:00
2025-02-10 18:21:44 +08:00
}
func removeSpotLossAndAddPosition ( preOrder * DbModels . LinePreOrder ) {
2025-02-06 18:03:09 +08:00
stoplossKey := fmt . Sprintf ( rediskey . SpotStopLossList , global . EXCHANGE_BINANCE )
stoplossVal , _ := helper . DefaultRedis . GetAllList ( stoplossKey )
2025-02-10 18:21:44 +08:00
addPositionKey := fmt . Sprintf ( rediskey . SpotAddPositionList , global . EXCHANGE_BINANCE )
addPositionVal , _ := helper . DefaultRedis . GetAllList ( addPositionKey )
2025-02-11 14:49:16 +08:00
reduceKey := fmt . Sprintf ( rediskey . SpotReduceList , global . EXCHANGE_BINANCE )
reduceVal , _ := helper . DefaultRedis . GetAllList ( reduceKey )
2025-02-10 18:21:44 +08:00
stoploss := dto . StopLossRedisList { }
addPosition := AddPositionList { }
2025-02-11 14:49:16 +08:00
reduce := ReduceListItem { }
2025-02-06 18:03:09 +08:00
2025-02-11 14:49:16 +08:00
//止损缓存
2025-02-06 18:03:09 +08:00
for _ , v := range stoplossVal {
sonic . Unmarshal ( [ ] byte ( v ) , & stoploss )
2025-02-10 18:21:44 +08:00
if stoploss . MainId == preOrder . MainId {
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 {
2025-02-10 18:21:44 +08:00
logger . Errorf ( "订单回调失败, 回调订单号:%s 删除止损缓存失败:%v" , preOrder . OrderSn , err )
}
}
}
2025-02-11 14:49:16 +08:00
//加仓缓存
2025-02-10 18:21:44 +08:00
for _ , v := range addPositionVal {
sonic . Unmarshal ( [ ] byte ( v ) , & addPosition )
if addPosition . MainId == preOrder . MainId {
_ , err := helper . DefaultRedis . LRem ( addPositionKey , v )
if err != nil {
logger . Errorf ( "订单回调失败, 回调订单号:%s 删除加仓缓存失败:%v" , preOrder . OrderSn , err )
2025-02-06 18:03:09 +08:00
}
}
}
2025-02-11 14:49:16 +08:00
//减仓缓存
for _ , v := range reduceVal {
sonic . Unmarshal ( [ ] byte ( v ) , & reduce )
if reduce . MainId == preOrder . MainId {
_ , err := helper . DefaultRedis . LRem ( reduceKey , v )
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-10 18:21:44 +08:00
tradeSet , _ := GetTradeSet ( preOrder . Symbol , 0 )
if tradeSet . Coin == "" {
logger . Errorf ( "获取交易对配置失败, 回调订单号:%s" , preOrder . OrderSn )
return
}
//主单类型
if preOrder . OrderCategory == 1 {
//预生成加仓单
orderExts , err := GetOrderExts ( db , preOrder . Id )
if err != nil {
logger . Errorf ( "预生成加仓单失败, 回调订单号:%s 获取主单拓展配置失败:%v" , preOrder . OrderSn , err )
return
}
price := utility . StrToDecimal ( preOrder . Price )
for _ , v := range orderExts {
if v . OrderId == 0 {
var data DbModels . LinePreOrder
2025-02-14 09:43:49 +08:00
copier . Copy ( & data , & preOrder )
2025-02-10 18:21:44 +08:00
data . Id = 0
data . Pid = preOrder . Id
data . OrderSn = utility . Int64ToString ( snowflakehelper . GetOrderId ( ) )
data . MainId = preOrder . Id
data . CreatedAt = time . Now ( )
data . MainOrderType = v . AddPositionOrderType
data . Status = 0
data . OrderCategory = 3
2025-02-14 09:43:49 +08:00
data . Rate = v . AddPositionPriceRatio . String ( )
2025-02-10 18:21:44 +08:00
var percentage decimal . Decimal
if data . Site == "BUY" {
2025-02-14 09:43:49 +08:00
percentage = decimal . NewFromInt ( 1 ) . Sub ( v . AddPositionPriceRatio . Div ( decimal . NewFromInt ( 100 ) ) )
} else {
2025-02-11 18:03:30 +08:00
percentage = decimal . NewFromInt ( 1 ) . Add ( v . AddPositionPriceRatio . Div ( decimal . NewFromInt ( 100 ) ) )
2025-02-14 09:43:49 +08:00
}
dataPrice := price . Mul ( percentage ) . Truncate ( int32 ( tradeSet . PriceDigit ) )
data . Price = dataPrice . String ( )
if v . AddPositionType == 1 {
data . Num = preOrder . Num
data . BuyPrice = "0"
2025-02-10 18:21:44 +08:00
} else {
2025-02-14 09:43:49 +08:00
data . BuyPrice = v . AddPositionVal . String ( )
data . Num = v . AddPositionVal . Div ( dataPrice ) . Truncate ( int32 ( tradeSet . AmountDigit ) ) . String ( )
2025-02-10 18:21:44 +08:00
}
err := db . Transaction ( func ( tx * gorm . DB ) error {
if err2 := tx . Create ( & data ) . Error ; err2 != nil {
return err2
}
v . OrderId = data . Id
if err2 := tx . Model ( & v ) . Update ( "order_id" , data . Id ) . Error ; err2 != nil {
return err2
}
return nil
} )
if err != nil {
logger . Errorf ( "预生成加仓单失败, 回调订单号:%s 预生成加仓单失败:%v" , preOrder . OrderSn , err )
return
}
}
}
}
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 {
2025-02-14 09:43:49 +08:00
num := totalAmount . Mul ( decimal . NewFromFloat ( 0.998 ) )
2025-02-06 11:14:33 +08:00
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-11 14:49:16 +08:00
tradeSet , _ := GetTradeSet ( preOrder . Symbol , 0 )
if tradeSet . Coin == "" {
logger . Error ( "获取交易对失败" )
return
}
2025-02-11 18:03:30 +08:00
apiInfo , err := GetApiInfo ( preOrder . ApiId )
if apiInfo . Id == 0 {
logger . Error ( "订单回调查询apiuserinfo失败 err:" , err )
return
}
2025-02-14 09:43:49 +08:00
if preOrder . OrderCategory == 3 {
if err := cancelSymbolTakeAndStop ( db , preOrder . MainId , preOrder . SymbolType ) ; err != nil {
logger . Errorf ( "取消止盈止损订单失败 orderSn:%s err:%v" , preOrder . OrderSn , err )
}
2025-02-11 18:03:30 +08:00
}
2025-02-14 09:43:49 +08:00
num := getSpotPositionAvailableQuantity ( db , apiInfo , preOrder , tradeSet )
2025-02-11 14:49:16 +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 && errors . Is ( err , gorm . ErrRecordNotFound ) {
2025-02-06 11:14:33 +08:00
logger . Error ( "订单回调查询止盈止损单失败:" , err )
return
2025-02-11 14:49:16 +08:00
} else if len ( orders ) == 0 && preOrder . OrderCategory == 3 {
2025-02-14 09:43:49 +08:00
orders , err = makeSpotTakeAndReduce ( preOrder , db , tradeSet , num )
2025-02-11 14:49:16 +08:00
if err != nil {
return
}
2025-02-06 11:14:33 +08:00
}
spotApi := SpotRestApi { }
2025-02-14 09:43:49 +08:00
for _ , order := range orders {
2025-02-11 18:03:30 +08:00
order . Num = num . Mul ( decimal . NewFromFloat ( 0.998 ) ) . Truncate ( int32 ( tradeSet . AmountDigit ) ) . String ( )
if order . OrderType == 4 {
ext := DbModels . LinePreOrderExt { }
db . Model ( & ext ) . Where ( "order_id=?" , preOrder . Id ) . Find ( & ext )
if ext . ReduceNumRatio . Cmp ( decimal . Zero ) > 0 {
order . Num = num . Mul ( ext . ReduceNumRatio . Div ( decimal . NewFromInt ( 100 ) ) ) . Truncate ( int32 ( tradeSet . AmountDigit ) ) . String ( )
}
}
if err := db . Model ( & order ) . Update ( "num" , order . Num ) . Error ; err != nil {
2025-02-06 18:03:09 +08:00
logger . Errorf ( "修改止盈止损数量失败 订单号:%s err:%v" , order . OrderSn , err )
}
2025-02-06 11:14:33 +08:00
switch order . OrderType {
case 1 : // 止盈
2025-02-11 18:03:30 +08:00
processTakeProfitOrder ( db , spotApi , order )
2025-02-06 11:14:33 +08:00
case 2 : // 止损
2025-02-06 18:03:09 +08:00
processStopLossOrder ( order )
2025-02-11 14:49:16 +08:00
case 4 : //减仓
2025-02-11 18:03:30 +08:00
processSpotReduceOrder ( order )
2025-02-11 14:49:16 +08:00
}
}
}
// 构建现货止盈、减仓单
2025-02-14 09:43:49 +08:00
func makeSpotTakeAndReduce ( preOrder * DbModels . LinePreOrder , db * gorm . DB , tradeSet models2 . TradeSet , num decimal . Decimal ) ( [ ] DbModels . LinePreOrder , error ) {
2025-02-11 14:49:16 +08:00
ext := models . LinePreOrderExt { }
price := utility . StrToDecimal ( preOrder . Price )
2025-02-14 09:43:49 +08:00
orders := make ( [ ] DbModels . LinePreOrder , 0 )
2025-02-11 14:49:16 +08:00
if err := db . Model ( & ext ) . Where ( "order_id = ?" , preOrder . Id ) . First ( & ext ) . Error ; err != nil {
logger . Error ( "订单回调查询止盈止损单扩展表失败:" , err )
return nil , errors . New ( "订单回调查询止盈止损单扩展表失败" )
}
totalLossAmountU , _ := GetTotalLossAmount ( db , preOrder . MainId )
//止盈单
if ext . TakeProfitRatio . Cmp ( decimal . Zero ) > 0 {
profitOrder := models . LinePreOrder { }
copier . Copy ( & profitOrder , preOrder )
2025-02-14 09:43:49 +08:00
profitOrder . Id = 0
2025-02-11 14:49:16 +08:00
profitOrder . OrderSn = strconv . FormatInt ( snowflakehelper . GetOrderId ( ) , 10 )
profitOrder . Pid = preOrder . Id
profitOrder . OrderType = 1
profitOrder . Status = 0
profitOrder . Rate = ext . TakeProfitRatio . String ( )
profitOrder . MainId = ext . MainOrderId
profitOrder . Num = num . String ( )
//止盈需要累加之前的亏损
if totalLossAmountU . Cmp ( decimal . Zero ) > 0 {
2025-02-15 18:38:58 +08:00
percent := totalLossAmountU . Div ( num ) . Div ( price ) . Abs ( )
2025-02-14 09:43:49 +08:00
profitOrder . Rate = percent . Mul ( decimal . NewFromInt ( 100 ) ) . Add ( ext . TakeProfitRatio ) . Truncate ( 2 ) . String ( )
}
if strings . ToUpper ( preOrder . Site ) == "BUY" {
profitOrder . Site = "SELL"
2025-02-11 14:49:16 +08:00
} else {
2025-02-14 09:43:49 +08:00
profitOrder . Site = "BUY"
2025-02-11 14:49:16 +08:00
}
2025-02-14 09:43:49 +08:00
setPrice ( & profitOrder , preOrder , tradeSet )
2025-02-11 14:49:16 +08:00
orders = append ( orders , profitOrder )
}
//减仓单
if ext . ReducePriceRatio . Cmp ( decimal . Zero ) > 0 {
stopOrder := models . LinePreOrder { }
copier . Copy ( & stopOrder , preOrder )
2025-02-14 09:43:49 +08:00
stopOrder . Id = 0
2025-02-11 14:49:16 +08:00
stopOrder . OrderSn = strconv . FormatInt ( snowflakehelper . GetOrderId ( ) , 10 )
stopOrder . Pid = preOrder . Id
stopOrder . MainId = ext . MainOrderId
stopOrder . OrderType = 4
stopOrder . Status = 0
stopOrder . Rate = ext . ReducePriceRatio . String ( )
2025-02-11 18:03:30 +08:00
if ext . ReduceNumRatio . Cmp ( decimal . Zero ) > 0 {
stopOrder . Num = num . Mul ( ext . ReduceNumRatio . Div ( decimal . NewFromInt ( 100 ) ) ) . Truncate ( int32 ( tradeSet . AmountDigit ) ) . String ( )
} else {
stopOrder . Num = num . String ( )
}
2025-02-11 14:49:16 +08:00
if strings . ToUpper ( preOrder . Site ) == "BUY" {
2025-02-14 09:43:49 +08:00
stopOrder . Site = "SELL"
2025-02-11 14:49:16 +08:00
} else {
2025-02-14 09:43:49 +08:00
stopOrder . Site = "BUY"
2025-02-06 11:14:33 +08:00
}
2025-02-14 09:43:49 +08:00
setPrice ( & stopOrder , preOrder , tradeSet )
orders = append ( orders , stopOrder )
2025-02-06 11:14:33 +08:00
}
2025-02-11 14:49:16 +08:00
if len ( orders ) > 0 {
if err := db . Create ( & orders ) . Error ; err != nil {
logger . Error ( "主单回调,创建止盈、减仓单失败" )
return orders , errors . New ( "主单回调,创建止盈、减仓单失败" )
}
}
return orders , nil
}
2025-02-14 09:43:49 +08:00
// 根据下单百分比计算价格
func setPrice ( order * models . LinePreOrder , preOrder * models . LinePreOrder , tradeSet models2 . TradeSet ) {
orderType := order . OrderType
itemSide := order . Site
rate := utility . StrToDecimal ( order . Rate )
switch {
//做多止盈、做空止损或减仓
case ( orderType == 1 && itemSide == "SELL" ) , ( ( orderType == 2 || orderType == 4 ) && itemSide == "BUY" ) :
order . Price = utility . StrToDecimal ( preOrder . Price ) . Mul ( decimal . NewFromInt ( 1 ) . Add ( rate . Div ( decimal . NewFromInt ( 100 ) ) ) ) . Truncate ( int32 ( tradeSet . PriceDigit ) ) . String ( )
//做多止损或减仓、做空止盈
case ( ( orderType == 2 || orderType == 4 ) && itemSide == "SELL" ) , ( orderType == 1 && itemSide == "BUY" ) :
order . Price = utility . StrToDecimal ( preOrder . Price ) . Mul ( decimal . NewFromInt ( 1 ) . Sub ( rate . Div ( decimal . NewFromInt ( 100 ) ) ) ) . Truncate ( int32 ( tradeSet . PriceDigit ) ) . String ( )
}
}
2025-02-11 14:49:16 +08:00
// 现货减仓
2025-02-11 18:03:30 +08:00
func processSpotReduceOrder ( preOrder DbModels . LinePreOrder ) {
2025-02-11 14:49:16 +08:00
key := fmt . Sprintf ( rediskey . SpotReduceList , global . EXCHANGE_BINANCE )
item := ReduceListItem {
Id : preOrder . Id ,
ApiId : preOrder . ApiId ,
Pid : preOrder . Pid ,
MainId : preOrder . MainId ,
Price : utility . StrToDecimal ( preOrder . Price ) ,
2025-02-11 18:03:30 +08:00
Num : utility . StrToDecimal ( preOrder . Num ) ,
2025-02-11 14:49:16 +08:00
Side : preOrder . Site ,
Symbol : preOrder . Symbol ,
OrderSn : preOrder . OrderSn ,
}
itemVal , err := sonic . MarshalString ( & item )
if err != nil {
logger . Errorf ( "序列化失败 err:%v" , err )
return
}
if err := helper . DefaultRedis . RPushList ( key , itemVal ) ; err != nil {
logger . Errorf ( "减仓单写入缓存失败 err:%v" , err )
return
}
2025-02-06 11:14:33 +08:00
}
// 处理止盈订单
2025-02-11 18:03:30 +08:00
func processTakeProfitOrder ( db * gorm . DB , spotApi SpotRestApi , order models . LinePreOrder ) {
2025-02-06 11:14:33 +08:00
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 ) ) ,
2025-02-11 18:03:30 +08:00
Quantity : utility . StrToDecimal ( order . Num ) ,
2025-02-06 11:14:33 +08:00
Type : "TAKE_PROFIT_LIMIT" ,
TimeInForce : "GTC" ,
StopPrice : price . Truncate ( int32 ( tradeSet . PriceDigit ) ) ,
NewClientOrderId : order . OrderSn ,
}
2025-02-14 09:43:49 +08:00
err := spotApi . OrderPlaceLoop ( db , params , 3 )
2025-02-06 11:14:33 +08:00
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 ) .
2025-02-11 18:03:30 +08:00
Updates ( map [ string ] interface { } { "status" : "1" , "num" : order . Num } ) . Error ; err != nil {
2025-02-06 11:14:33 +08:00
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 ,
2025-02-15 18:38:58 +08:00
MainId : order . MainId ,
2025-02-06 18:03:09 +08:00
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
}
2025-02-14 09:43:49 +08:00
// 循环取消订单
func CancelOpenOrderByOrderSnLoop ( apiInfo DbModels . LineApiUser , symbol string , orderSn string ) error {
spotApi := SpotRestApi { }
var err error
for x := 1 ; x <= 4 ; x ++ {
err = spotApi . CancelOpenOrderByOrderSn ( apiInfo , symbol , orderSn )
if err == nil || strings . Contains ( err . Error ( ) , "该交易对没有订单" ) {
err = nil
break
}
}
return err
}
2025-02-06 11:14:33 +08:00
// NEW
// PENDING_NEW
// PARTIALLY_FILLED
// FILLED
// CANCELED
// PENDING_CANCEL
// REJECTED
// EXPIRED
// EXPIRED_IN_MATCH