351 lines
11 KiB
Go
351 lines
11 KiB
Go
package binanceservice
|
||
|
||
import (
|
||
"errors"
|
||
"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"
|
||
"go-admin/pkg/utility"
|
||
"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/go-admin-team/go-admin-core/sdk"
|
||
"github.com/shopspring/decimal"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type AddPosition struct {
|
||
Db *gorm.DB
|
||
}
|
||
|
||
// 获取缓存交易对
|
||
// symbolType 0-现货 1-合约
|
||
func GetTradeSet(symbol string, symbolType int) (models.TradeSet, error) {
|
||
result := models.TradeSet{}
|
||
val := ""
|
||
|
||
switch symbolType {
|
||
case 0:
|
||
key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol)
|
||
val, _ = helper.DefaultRedis.GetString(key)
|
||
case 1:
|
||
key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, symbol)
|
||
val, _ = helper.DefaultRedis.GetString(key)
|
||
}
|
||
|
||
if val != "" {
|
||
if err := sonic.Unmarshal([]byte(val), &result); err != nil {
|
||
return result, err
|
||
}
|
||
} else {
|
||
return result, errors.New("未找到交易对信息")
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
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 := 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
|
||
}
|
||
|
||
// CalculateAmount 计算加仓数量
|
||
func (e *AddPosition) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice decimal.Decimal, amountDigit int, notUsdt bool, symbolInfo DbModels.LineSymbol) (decimal.Decimal, error) {
|
||
var amt decimal.Decimal
|
||
if req.CoverType == 1 {
|
||
decimalValue := utility.StringToDecimal(req.Value).Div(decimal.NewFromInt(100))
|
||
amt = totalNum.Mul(decimalValue)
|
||
} else {
|
||
decimalValue := utility.StringToDecimal(req.Value)
|
||
if notUsdt {
|
||
tickerSymbolMaps := make([]dto.Ticker, 0)
|
||
tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val()
|
||
if err := sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps); err != nil {
|
||
return decimal.Zero, err
|
||
}
|
||
|
||
var tickerPrice decimal.Decimal
|
||
for _, symbolMap := range tickerSymbolMaps {
|
||
if symbolMap.Symbol == strings.ToUpper(symbolInfo.BaseAsset+"USDT") {
|
||
tickerPrice, _ = decimal.NewFromString(symbolMap.Price)
|
||
break
|
||
}
|
||
}
|
||
|
||
for _, symbolMap := range tickerSymbolMaps {
|
||
if symbolMap.Symbol == strings.ToUpper(symbolInfo.QuoteAsset+"USDT") {
|
||
uTickerPrice, _ := decimal.NewFromString(symbolMap.Price)
|
||
div := tickerPrice.Div(decimal.NewFromInt(1).Div(uTickerPrice))
|
||
amt = decimalValue.Div(div)
|
||
break
|
||
}
|
||
}
|
||
} else {
|
||
amt = decimalValue.Div(lastPrice)
|
||
}
|
||
}
|
||
return amt.Truncate(int32(amountDigit)), nil
|
||
}
|
||
|
||
// 主单平仓删除缓存
|
||
// mainOrderId 主单id
|
||
// coverType 1现货->合约 2->合约->合约 3合约->现货
|
||
func MainClosePositionClearCache(mainOrderId int, coverType int) {
|
||
if coverType == 1 {
|
||
spotStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotStopLossList)
|
||
spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotAddPositionList)
|
||
var position 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.Pid == mainOrderId {
|
||
helper.DefaultRedis.LRem(rediskey.SpotAddPositionList, item)
|
||
}
|
||
}
|
||
|
||
for _, item := range spotStopArray {
|
||
if err := sonic.Unmarshal([]byte(item), &stop); err != nil {
|
||
log.Error("MainClosePositionClearCache Unmarshal err:", err)
|
||
}
|
||
|
||
if stop.PId == mainOrderId {
|
||
helper.DefaultRedis.LRem(rediskey.SpotStopLossList, item)
|
||
}
|
||
}
|
||
|
||
} else {
|
||
futAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesAddPositionList)
|
||
futStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesStopLossList)
|
||
var position 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.Pid == mainOrderId {
|
||
helper.DefaultRedis.LRem(rediskey.FuturesAddPositionList, item)
|
||
}
|
||
}
|
||
|
||
for _, item := range futStopArray {
|
||
if err := sonic.Unmarshal([]byte(item), &stop); err != nil {
|
||
log.Error("MainClosePositionClearCache Unmarshal err:", err)
|
||
}
|
||
|
||
if stop.PId == mainOrderId {
|
||
helper.DefaultRedis.LRem(rediskey.FuturesStopLossList, item)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取数据库连接
|
||
func GetDBConnection() *gorm.DB {
|
||
dbs := sdk.Runtime.GetDb()
|
||
for _, db := range dbs {
|
||
return db
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// 查询订单
|
||
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
|
||
}
|