409 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package binanceservice
 | ||
| 
 | ||
| import (
 | ||
| 	"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/positiondto"
 | ||
| 	"go-admin/pkg/utility"
 | ||
| 	"go-admin/services/commonservice"
 | ||
| 	"go-admin/services/positionservice"
 | ||
| 	"strings"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/bytedance/sonic"
 | ||
| 	"github.com/go-admin-team/go-admin-core/logger"
 | ||
| 	log "github.com/go-admin-team/go-admin-core/logger"
 | ||
| 	"github.com/shopspring/decimal"
 | ||
| 	"gorm.io/gorm"
 | ||
| )
 | ||
| 
 | ||
| type AddPosition struct {
 | ||
| 	Db *gorm.DB
 | ||
| }
 | ||
| 
 | ||
| func GetRisk(futApi *FutRestApi, apiInfo *DbModels.LineApiUser, symbol string) []PositionRisk {
 | ||
| 	for x := 0; x < 5; x++ {
 | ||
| 		risks, _ := futApi.GetPositionV3(apiInfo, symbol)
 | ||
| 		if len(risks) > 0 {
 | ||
| 			return risks
 | ||
| 		}
 | ||
| 		time.Sleep(time.Millisecond * 200)
 | ||
| 	}
 | ||
| 
 | ||
| 	return []PositionRisk{}
 | ||
| }
 | ||
| 
 | ||
| // CancelFutClosePosition 撤销交易对指定方向的平仓单
 | ||
| // apiUserInfo 账户信息
 | ||
| // symbol 交易对
 | ||
| // side 购买反向
 | ||
| // positionSide 仓位方向
 | ||
| // side=BUY positionSide=SHORT 就是撤销平空委托
 | ||
| // side=BUY&positionSide=LONG是开多,
 | ||
| // side=SELL&positionSide=LONG是平多,
 | ||
| // side=SELL&positionSide=SHORT是开空,
 | ||
| // side=BUY&positionSide=SHORT是平空。
 | ||
| func CancelFutClosePosition(apiUserInfo DbModels.LineApiUser, symbol string, side string, positionSide string) error {
 | ||
| 	//查询当前用户的委托订单
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	resp, _, err := client.SendFuturesRequestAuth("/fapi/v1/openOrders", "GET", map[string]string{
 | ||
| 		"symbol":     symbol,
 | ||
| 		"recvWindow": "5000",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		logger.Error("撤销平仓单时查询委托失败:", err)
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	var openOrders []OpenOrders
 | ||
| 	sonic.Unmarshal(resp, &openOrders)
 | ||
| 	orderIdList := make([]int, 0)
 | ||
| 	for _, order := range openOrders {
 | ||
| 		if order.Side == side && order.PositionSide == positionSide {
 | ||
| 			orderIdList = append(orderIdList, order.OrderId)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	// 每次取 10 个元素
 | ||
| 	batchSize := 10
 | ||
| 	for i := 0; i < len(orderIdList); i += batchSize {
 | ||
| 		end := i + batchSize
 | ||
| 		if end > len(orderIdList) {
 | ||
| 			end = len(orderIdList) // 避免越界
 | ||
| 		}
 | ||
| 		// 取出当前批次的元素
 | ||
| 		batch := orderIdList[i:end]
 | ||
| 		marshal, _ := sonic.Marshal(&batch)
 | ||
| 		_, _, err = client.SendFuturesRequestAuth("/fapi/v1/batchOrders", "DELETE", map[string]string{
 | ||
| 			"symbol":      symbol,
 | ||
| 			"orderIdList": string(marshal),
 | ||
| 			"recvWindow":  "5000",
 | ||
| 		})
 | ||
| 		if err != nil {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // CancelSpotClosePosition 取消现货平仓单(等同于撤销卖单)
 | ||
| // apiUserInfo: Api用户信息
 | ||
| // symbol: 交易对
 | ||
| func CancelSpotClosePosition(apiUserInfo *DbModels.LineApiUser, symbol string) error {
 | ||
| 
 | ||
| 	return CancelSpotOrder(symbol, apiUserInfo, "SELL")
 | ||
| }
 | ||
| 
 | ||
| // 取消现货订单
 | ||
| func CancelSpotOrder(symbol string, apiUserInfo *DbModels.LineApiUser, side string) error {
 | ||
| 	searchEndpoint := "/api/v3/openOrders"
 | ||
| 	cencelEndpoint := "/api/v3/order"
 | ||
| 	searchParams := map[string]interface{}{
 | ||
| 		"symbol": symbol,
 | ||
| 	}
 | ||
| 	client := GetClient(apiUserInfo)
 | ||
| 	resp, _, err := client.SendSpotAuth(searchEndpoint, "GET", searchParams)
 | ||
| 	db := commonservice.GetDBConnection()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		if len(resp) > 0 {
 | ||
| 
 | ||
| 		} else {
 | ||
| 			logger.Error("查询现货当前下单失败:", err)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	var openOrders []map[string]interface{}
 | ||
| 	err = sonic.Unmarshal(resp, &openOrders)
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, order := range openOrders {
 | ||
| 		if orderSymbol, ok := order["symbol"].(string); ok && orderSymbol == symbol {
 | ||
| 			if orderSide, ok := order["side"].(string); ok && orderSide == side {
 | ||
| 				orderId, ok := order["orderId"].(float64)
 | ||
| 
 | ||
| 				if !ok {
 | ||
| 					continue
 | ||
| 				}
 | ||
| 
 | ||
| 				orderSn, _ := order["clientOrderId"].(string)
 | ||
| 				params := map[string]string{
 | ||
| 					"symbol":  orderSymbol,
 | ||
| 					"orderId": utility.Float64CutString(orderId, 0),
 | ||
| 					// "cancelRestrictions": "ONLY_NEW",
 | ||
| 				}
 | ||
| 				_, _, err = client.SendSpotAuth(cencelEndpoint, "DELETE", params)
 | ||
| 
 | ||
| 				if err != nil {
 | ||
| 					logger.Error("撤销指定现货平仓单失败 ordersn:", orderSn, " orderId:", orderId, " err:", err)
 | ||
| 				} else {
 | ||
| 					if err := db.Model(&DbModels.LinePreOrder{}).Where("order_sn = ? and status !='9'", orderSn).Update("status", "4").Error; err != nil {
 | ||
| 						log.Error("修改止盈单撤销状态失败:", err)
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 		return nil
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // GetTargetSymbol 获取目标交易对信息
 | ||
| func (e *AddPosition) GetTargetSymbol(symbol string, symbolType int) (string, bool, DbModels.LineSymbol, error) {
 | ||
| 	var targetSymbol string
 | ||
| 	var notUsdt bool
 | ||
| 	var symbolInfo DbModels.LineSymbol
 | ||
| 
 | ||
| 	// 处理非 USDT 交易对
 | ||
| 	if !strings.HasSuffix(symbol, "USDT") {
 | ||
| 		notUsdt = true
 | ||
| 		if err := e.Db.Model(&DbModels.LineSymbol{}).Where("symbol = ? AND type = ?", symbol, utility.IntToString(symbolType)).Find(&symbolInfo).Error; err != nil {
 | ||
| 			return "", false, DbModels.LineSymbol{}, err
 | ||
| 		}
 | ||
| 		if symbolInfo.Id <= 0 {
 | ||
| 			return "", false, DbModels.LineSymbol{}, fmt.Errorf("未找到交易对信息")
 | ||
| 		}
 | ||
| 		targetSymbol = symbolInfo.BaseAsset + "USDT"
 | ||
| 	} else {
 | ||
| 		targetSymbol = symbol
 | ||
| 	}
 | ||
| 
 | ||
| 	return targetSymbol, notUsdt, symbolInfo, nil
 | ||
| }
 | ||
| 
 | ||
| func (e *AddPosition) GetOrderInfo(req dto.ManuallyCover, symbol, orderType, site, status string) (DbModels.LinePreOrder, error) {
 | ||
| 	var orderInfo DbModels.LinePreOrder
 | ||
| 	if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND site = ? AND status = ?", req.ApiId, symbol, orderType, site, status).Find(&orderInfo).Error; err != nil {
 | ||
| 		return DbModels.LinePreOrder{}, err
 | ||
| 	}
 | ||
| 	if orderInfo.Id <= 0 {
 | ||
| 		return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
 | ||
| 	}
 | ||
| 	return orderInfo, nil
 | ||
| }
 | ||
| 
 | ||
| func (e *AddPosition) GetFutOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (DbModels.LinePreOrder, error) {
 | ||
| 	var orderInfo DbModels.LinePreOrder
 | ||
| 	if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 2", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil {
 | ||
| 		return DbModels.LinePreOrder{}, err
 | ||
| 	}
 | ||
| 	if orderInfo.Id <= 0 {
 | ||
| 		return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
 | ||
| 	}
 | ||
| 	return orderInfo, nil
 | ||
| }
 | ||
| 
 | ||
| // GetFutSpotOrderInfo 获取合约对现货的订单信息
 | ||
| func (e *AddPosition) GetFutSpotOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (DbModels.LinePreOrder, error) {
 | ||
| 	var orderInfo DbModels.LinePreOrder
 | ||
| 	if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 3", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil {
 | ||
| 		return DbModels.LinePreOrder{}, err
 | ||
| 	}
 | ||
| 	if orderInfo.Id <= 0 {
 | ||
| 		return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息")
 | ||
| 	}
 | ||
| 	return orderInfo, nil
 | ||
| }
 | ||
| 
 | ||
| // 主单平仓删除缓存
 | ||
| // mainOrderId 主单id
 | ||
| // symbolType 1现货 2合约
 | ||
| func MainClosePositionClearCache(mainId int, symbolType int) {
 | ||
| 	if symbolType == 1 {
 | ||
| 		keySpotStop := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE)
 | ||
| 		keySpotAddposition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE)
 | ||
| 		spotStopArray, _ := helper.DefaultRedis.GetAllList(keySpotStop)
 | ||
| 		spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(keySpotAddposition)
 | ||
| 		var position positiondto.AddPositionList
 | ||
| 		var stop dto.StopLossRedisList
 | ||
| 
 | ||
| 		for _, item := range spotAddpositionArray {
 | ||
| 			if err := sonic.Unmarshal([]byte(item), &position); err != nil {
 | ||
| 				log.Error("MainClosePositionClearCache Unmarshal err:", err)
 | ||
| 			}
 | ||
| 
 | ||
| 			if position.MainId == mainId {
 | ||
| 				helper.DefaultRedis.LRem(keySpotAddposition, item)
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		for _, item := range spotStopArray {
 | ||
| 			if err := sonic.Unmarshal([]byte(item), &stop); err != nil {
 | ||
| 				log.Error("MainClosePositionClearCache Unmarshal err:", err)
 | ||
| 			}
 | ||
| 
 | ||
| 			if stop.MainId == mainId {
 | ||
| 				helper.DefaultRedis.LRem(keySpotStop, item)
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 	} else {
 | ||
| 		keyFutStop := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE)
 | ||
| 		keyFutAddposition := fmt.Sprintf(rediskey.FuturesStopLossList, global.EXCHANGE_BINANCE)
 | ||
| 		futAddpositionArray, _ := helper.DefaultRedis.GetAllList(keyFutStop)
 | ||
| 		futStopArray, _ := helper.DefaultRedis.GetAllList(keyFutAddposition)
 | ||
| 		var position positiondto.AddPositionList
 | ||
| 		var stop dto.StopLossRedisList
 | ||
| 
 | ||
| 		for _, item := range futAddpositionArray {
 | ||
| 			if err := sonic.Unmarshal([]byte(item), &position); err != nil {
 | ||
| 				log.Error("MainClosePositionClearCache Unmarshal err:", err)
 | ||
| 			}
 | ||
| 
 | ||
| 			if position.MainId == mainId {
 | ||
| 				helper.DefaultRedis.LRem(keyFutAddposition, item)
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		for _, item := range futStopArray {
 | ||
| 			if err := sonic.Unmarshal([]byte(item), &stop); err != nil {
 | ||
| 				log.Error("MainClosePositionClearCache Unmarshal err:", err)
 | ||
| 			}
 | ||
| 
 | ||
| 			if stop.MainId == mainId {
 | ||
| 				helper.DefaultRedis.LRem(keyFutStop, item)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // 查询订单
 | ||
| func getPreOrder(db *gorm.DB, orderSn interface{}) (*DbModels.LinePreOrder, error) {
 | ||
| 	preOrder := &DbModels.LinePreOrder{}
 | ||
| 	if err := db.Model(preOrder).Where("order_sn = ?", orderSn).First(preOrder).Error; err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 	return preOrder, nil
 | ||
| }
 | ||
| 
 | ||
| // 取消主单下的止盈止损
 | ||
| // mainId 主单id
 | ||
| // symbolType 1现货 2合约
 | ||
| func cancelSymbolTakeAndStop(db *gorm.DB, mainId int, symbolType int) error {
 | ||
| 	orders, err := GetSymbolTakeAndStop(db, mainId, symbolType)
 | ||
| 	errStr := ""
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, order := range orders {
 | ||
| 		apiUserInfo, _ := GetApiInfo(order.ApiId)
 | ||
| 		if apiUserInfo.Id == 0 {
 | ||
| 			logger.Errorf("cancelSymbolTakeAndStop 查询api用户失败 apiid:%v ordersn:%s", order.ApiId, order.OrderSn)
 | ||
| 		}
 | ||
| 
 | ||
| 		switch {
 | ||
| 		case order.OrderType == 1 && symbolType == 1:
 | ||
| 			err = CancelOpenOrderByOrderSnLoop(apiUserInfo, order.Symbol, order.OrderSn)
 | ||
| 
 | ||
| 			if err != nil {
 | ||
| 				errStr += fmt.Sprintf("取消止盈失败,订单号:%s,错误:%v  ", order.OrderSn, err)
 | ||
| 			}
 | ||
| 		case order.OrderType == 2 && symbolType == 1:
 | ||
| 			key := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE)
 | ||
| 			stoplosss, _ := helper.DefaultRedis.GetAllList(key)
 | ||
| 			stop := dto.StopLossRedisList{}
 | ||
| 
 | ||
| 			for _, item := range stoplosss {
 | ||
| 				sonic.Unmarshal([]byte(item), &stop)
 | ||
| 
 | ||
| 				if stop.MainId == order.MainId {
 | ||
| 					_, err2 := helper.DefaultRedis.LRem(key, item)
 | ||
| 
 | ||
| 					if err2 != nil {
 | ||
| 						logger.Errorf("移除止损缓存失败 err:%v", err2)
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		case order.OrderType == 1 && symbolType == 2:
 | ||
| 			err = CancelFutOrderByOrderSnLoop(apiUserInfo, order.Symbol, order.OrderSn)
 | ||
| 
 | ||
| 			if err != nil {
 | ||
| 				errStr += fmt.Sprintf("取消止盈失败,订单号:%s,错误:%v  ", order.OrderSn, err)
 | ||
| 			}
 | ||
| 		case order.OrderType == 2 && symbolType == 2:
 | ||
| 			err = CancelFutOrderByOrderSnLoop(apiUserInfo, order.Symbol, order.OrderSn)
 | ||
| 
 | ||
| 			if err != nil {
 | ||
| 				errStr += fmt.Sprintf("取消止损失败,订单号:%s,错误:%v  ", order.OrderSn, err)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 保存仓位信息
 | ||
| func savePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) positiondto.PositionDto {
 | ||
| 	positionManage := positionservice.BinancePositionManagement{}
 | ||
| 	positionManage.Orm = db
 | ||
| 	positionReq := positiondto.PositionAddReq{
 | ||
| 		ApiId:      preOrder.ApiId,
 | ||
| 		SymbolType: preOrder.SymbolType,
 | ||
| 		Symbol:     preOrder.Symbol,
 | ||
| 		Price:      utility.StrToDecimal(preOrder.Price),
 | ||
| 		// Side:       preOrder.Site,
 | ||
| 		Quantity: utility.StrToDecimal(preOrder.Num),
 | ||
| 	}
 | ||
| 
 | ||
| 	switch {
 | ||
| 	case preOrder.OrderType == 0 && preOrder.Site == "BUY",
 | ||
| 		preOrder.OrderType != 0 && preOrder.Site == "SELL":
 | ||
| 		positionReq.PositionSide = "LONG"
 | ||
| 	case preOrder.OrderType == 0 && preOrder.Site == "SELL",
 | ||
| 		preOrder.OrderType != 0 && preOrder.Site == "BUY":
 | ||
| 		positionReq.PositionSide = "SHORT"
 | ||
| 	}
 | ||
| 
 | ||
| 	//减仓单 数量为负
 | ||
| 	if preOrder.OrderType != 0 {
 | ||
| 		if preOrder.Site == "BUY" {
 | ||
| 			positionReq.Side = "SELL"
 | ||
| 		} else {
 | ||
| 			positionReq.Side = "BUY"
 | ||
| 		}
 | ||
| 
 | ||
| 		positionReq.Quantity = positionReq.Quantity.Mul(decimal.NewFromInt(-1))
 | ||
| 	} else {
 | ||
| 		positionReq.Side = preOrder.Site
 | ||
| 	}
 | ||
| 
 | ||
| 	positionData, err := positionManage.SavePosition(&positionReq, global.EXCHANGE_BINANCE)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		logger.Error("保存持仓信息失败, 主单号:%s, 错误信息:%v", preOrder.OrderSn, err)
 | ||
| 	}
 | ||
| 	return positionData
 | ||
| }
 | ||
| 
 | ||
| // 获取已开仓的主单id
 | ||
| func getOpenPositionMainOrderId(db *gorm.DB, newId, apiId, symbolType int, exchangeType, symbol, side string) ([]DbModels.LinePreOrder, error) {
 | ||
| 	mainOrders := make([]DbModels.LinePreOrder, 0)
 | ||
| 
 | ||
| 	if err := db.Model(&DbModels.LinePreOrder{}).
 | ||
| 		Where("api_id =? AND status>4 AND order_type =0 AND status<7 AND symbol=? AND symbol_type =? AND site= ? AND exchange_type=? AND id!=?",
 | ||
| 			apiId, symbol, symbolType, side, exchangeType, newId).
 | ||
| 		Select("id", "main_id", "order_sn").Find(&mainOrders).Error; err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	return mainOrders, nil
 | ||
| }
 | ||
| 
 | ||
| // 获取需要取消的订单号
 | ||
| func GetOpenOrderSns(db *gorm.DB, mainIds []int) ([]string, error) {
 | ||
| 	result := []string{}
 | ||
| 
 | ||
| 	//委托中的订单
 | ||
| 	if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id IN ? AND status=5", mainIds).Select("order_sn").Find(&result).Error; err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	return result, nil
 | ||
| }
 |