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

409 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package binanceservice
import (
"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
}