982 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			982 lines
		
	
	
		
			30 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/models/binancedto"
 | ||
| 	"go-admin/models/futuresdto"
 | ||
| 	"go-admin/pkg/httputils"
 | ||
| 	"go-admin/pkg/retryhelper"
 | ||
| 	"go-admin/pkg/utility"
 | ||
| 	"strconv"
 | ||
| 	"strings"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/shopspring/decimal"
 | ||
| 	"gorm.io/gorm"
 | ||
| 
 | ||
| 	"github.com/bytedance/sonic"
 | ||
| 	"github.com/go-redis/redis/v8"
 | ||
| 
 | ||
| 	"github.com/go-admin-team/go-admin-core/logger"
 | ||
| 	log "github.com/go-admin-team/go-admin-core/logger"
 | ||
| )
 | ||
| 
 | ||
| const (
 | ||
| 	futureApi = "https://fapi.binance.com"
 | ||
| )
 | ||
| 
 | ||
| type FutRestApi struct {
 | ||
| }
 | ||
| 
 | ||
| var FutErrorMaps = map[float64]string{
 | ||
| 	-2021: "订单已拒绝。请调整触发价并重新下订单。 对于买入/做多,止盈订单触发价应低于市场价,止损订单的触发价应高于市场价。卖出/做空则与之相反",
 | ||
| 	-4164: "少于最小下单金额",
 | ||
| 	-4061: "持仓方向需要设置为单向持仓。",
 | ||
| 	-2019: "保证金不足",
 | ||
| 	-1111: "金额设置错误。精度错误",
 | ||
| 	-1021: "请求的时间戳在recvWindow之外",
 | ||
| 	-1106: "发送了不需要的参数。",
 | ||
| 	-1109: "非有效账户",
 | ||
| 	-1110: "交易对不正确",
 | ||
| 	-1112: "交易对没有挂单",
 | ||
| 	-1114: "发送的TimeInForce参数不需要。",
 | ||
| 	-1115: "无效的timeInForce",
 | ||
| 	-1116: "无效订单类型。",
 | ||
| 	-1117: "无效买卖方向。",
 | ||
| 	-1121: "无效的交易对。",
 | ||
| 	-2010: "新订单被拒绝",
 | ||
| 	-2011: "取消订单被拒绝",
 | ||
| 	-2012: "批量取消失败",
 | ||
| 	-2013: "订单不存在。",
 | ||
| 	-2014: "API-key 格式无效。",
 | ||
| 	-2015: "无效的API密钥,IP或操作权限。",
 | ||
| 	-2018: "余额不足",
 | ||
| 	-2020: "无法成交",
 | ||
| 	-2022: "ReduceOnly订单被拒绝",
 | ||
| 	-2023: "用户正处于被强平模式",
 | ||
| 	-2024: "持仓不足",
 | ||
| 	-2025: "挂单量达到上限",
 | ||
| 	-4001: "价格小于0",
 | ||
| 	-4002: "价格超过最大值",
 | ||
| 	-4003: "数量小于0",
 | ||
| 	-4004: "数量小于最小值",
 | ||
| 	-4005: "数量大于最大值",
 | ||
| 	-4008: "价格精度小于0",
 | ||
| 	-4009: "最大价格小于最小价格",
 | ||
| 	-4029: "价格精度小数点位数不正确。",
 | ||
| 	-4031: "不正确的参数类型。",
 | ||
| 	-4047: "如果有挂单,仓位模式不能切换。",
 | ||
| 	-4048: "如果有仓位,仓位模式不能切换。",
 | ||
| 	-4059: "无需变更仓位方向",
 | ||
| 	-4117: "stop订单在触发中",
 | ||
| }
 | ||
| 
 | ||
| var reduceOnlyRate = 0.95
 | ||
| 
 | ||
| /*
 | ||
| 获取资金费率
 | ||
| */
 | ||
| func GetPremiumIndex() ([]futuresdto.FundingInfo, error) {
 | ||
| 	url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/premiumIndex")
 | ||
| 	mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{})
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(mapData) == 0 {
 | ||
| 		return nil, errors.New("获取交易对失败,或数量为空")
 | ||
| 	}
 | ||
| 
 | ||
| 	var res []futuresdto.FundingInfo
 | ||
| 	err = sonic.Unmarshal([]byte(mapData), &res)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, item := range res {
 | ||
| 		key := fmt.Sprintf("%s:%s", global.FUNDING_INFO_FUTURES, item.Symbol)
 | ||
| 
 | ||
| 		if item.NextFundingTime == 0 {
 | ||
| 			helper.DefaultRedis.DeleteString(key)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		val, err := sonic.Marshal(item)
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			log.Error("序列化失败:", item.Symbol, err)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		err = helper.DefaultRedis.SetString(key, string(val))
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			log.Error("保存资金费率失败:", item.Symbol, err)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil, nil
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取指定交易对资金费率
 | ||
| 
 | ||
|   - @symbol 交易对名称
 | ||
|   - @data 结果
 | ||
| */
 | ||
| func GetFundingInfoBySymbol(symbol string, data *futuresdto.FundingInfo) error {
 | ||
| 	key := fmt.Sprintf("%s:%s", global.FUNDING_INFO_FUTURES, strings.ToUpper(symbol))
 | ||
| 	resData, err := helper.DefaultRedis.GetString(key)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	if resData == "" {
 | ||
| 		shouldReturn, err := GetAndReloadSymbol(symbol, data)
 | ||
| 		if shouldReturn {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	err = sonic.Unmarshal([]byte(resData), data)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	if data.NextFundingTime <= time.Now().Unix() {
 | ||
| 		shouldReturn, err := GetAndReloadSymbol(symbol, data)
 | ||
| 		if shouldReturn {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 获取并更新费率
 | ||
| func GetAndReloadSymbol(symbol string, data *futuresdto.FundingInfo) (bool, error) {
 | ||
| 	datas, err := GetPremiumIndex()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return true, err
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, item := range datas {
 | ||
| 		if item.Symbol == strings.ToUpper(symbol) {
 | ||
| 			*data = item
 | ||
| 
 | ||
| 			return true, nil
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return false, nil
 | ||
| }
 | ||
| 
 | ||
| // 获取合约交易对信息
 | ||
| func GetSymbols(data *futuresdto.FutExchangeInfo) error {
 | ||
| 	url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/exchangeInfo")
 | ||
| 	mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{})
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(mapData) == 0 {
 | ||
| 		return errors.New("获取合约交易对失败,或数量为空")
 | ||
| 	}
 | ||
| 
 | ||
| 	err = sonic.Unmarshal(mapData, data)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 获取并更新交易对
 | ||
| func GetAndReloadSymbols(data *map[string]models.TradeSet) error {
 | ||
| 	var info futuresdto.FutExchangeInfo
 | ||
| 	err := GetSymbols(&info)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	maps := make(map[string]string, 0)
 | ||
| 	if len(info.Symbols) > 0 {
 | ||
| 		for _, item := range info.Symbols {
 | ||
| 			if item.Status == "TRADING" && strings.HasSuffix(item.Symbol, "USDT") {
 | ||
| 				tradeSet := models.TradeSet{}
 | ||
| 				tradeSet.Coin = item.BaseAsset
 | ||
| 				tradeSet.Currency = item.QuoteAsset
 | ||
| 				for _, filter := range item.Filters {
 | ||
| 					switch filter.FilterType {
 | ||
| 					case "PRICE_FILTER":
 | ||
| 						tradeSet.PriceDigit = utility.GetPrecision(*filter.TickSize)
 | ||
| 						tradeSet.MinBuyVal = utility.StringAsFloat(*filter.MinPrice)
 | ||
| 					case "LOT_SIZE":
 | ||
| 						tradeSet.AmountDigit = utility.GetPrecision(*filter.StepSize)
 | ||
| 						tradeSet.MaxQty = utility.StringAsFloat(*filter.MaxQty)
 | ||
| 						tradeSet.MinQty = utility.StringAsFloat(*filter.MinQty)
 | ||
| 					case "MARKET_LOT_SIZE":
 | ||
| 						tradeSet.MarketMaxQty = utility.StringAsFloat(*filter.MaxQty)
 | ||
| 						tradeSet.MarketMinQty = utility.StringAsFloat(*filter.MinQty)
 | ||
| 					case "MIN_NOTIONAL":
 | ||
| 						tradeSet.MinNotional = *filter.Notional
 | ||
| 					case "MAX_NOTIONAL":
 | ||
| 						tradeSet.MaxNotional = *filter.Notional
 | ||
| 					}
 | ||
| 				}
 | ||
| 
 | ||
| 				(*data)[item.Symbol] = tradeSet
 | ||
| 
 | ||
| 				key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, item.Symbol)
 | ||
| 				val, err := sonic.Marshal(tradeSet)
 | ||
| 
 | ||
| 				if err != nil {
 | ||
| 					log.Error("合约交易对序列化失败", err)
 | ||
| 				} else {
 | ||
| 					maps[key] = string(val)
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(maps) > 0 {
 | ||
| 		if err = helper.DefaultRedis.BatchSet(&maps); err != nil {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| func (e FutRestApi) Ticker() {
 | ||
| 	url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/ticker/price")
 | ||
| 	mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{})
 | ||
| 	if err != nil {
 | ||
| 		log.Error("获取合约交易对行情失败:err:", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(mapData) == 0 {
 | ||
| 		log.Error("获取合约交易对行情失败,或数量为空")
 | ||
| 	}
 | ||
| 	helper.DefaultRedis.SetString(rediskey.FutSymbolTicker, string(mapData))
 | ||
| }
 | ||
| 
 | ||
| // 获取合约行情
 | ||
| func GetTicker24h() ([]futuresdto.FutureTicker24h, error) {
 | ||
| 	url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/ticker/24hr")
 | ||
| 
 | ||
| 	mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{})
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(mapData) == 0 {
 | ||
| 		return nil, errors.New("获取合约交易对24h行情失败,或数量为空")
 | ||
| 	}
 | ||
| 
 | ||
| 	var data []futuresdto.FutureTicker24h
 | ||
| 	err = sonic.Unmarshal(mapData, &data)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	return data, err
 | ||
| }
 | ||
| 
 | ||
| // 初始化交易对行情
 | ||
| func InitSymbolsTicker24h(maps *map[string]models.TradeSet) (deletes []string, err error) {
 | ||
| 	tickers, err := GetTicker24h()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return []string{}, err
 | ||
| 	}
 | ||
| 	deleteSymbol := make([]string, 0)
 | ||
| 	caches := make(map[string]string, 0)
 | ||
| 	priceChange := make([]*redis.Z, 0)
 | ||
| 
 | ||
| 	for _, item := range tickers {
 | ||
| 		symbol, exits := (*maps)[item.Symbol]
 | ||
| 
 | ||
| 		if !exits {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		symbol.OpenPrice = utility.StringAsFloat(item.OpenPrice)
 | ||
| 		symbol.PriceChange = utility.StringAsFloat(item.PriceChangePercent)
 | ||
| 		symbol.LowPrice = item.LowPrice
 | ||
| 		symbol.HighPrice = item.HighPrice
 | ||
| 		symbol.Volume = item.Volume
 | ||
| 		symbol.QuoteVolume = item.QuoteVolume
 | ||
| 		symbol.LastPrice = item.LastPrice
 | ||
| 
 | ||
| 		key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, item.Symbol)
 | ||
| 		if !strings.HasSuffix(item.Symbol, symbol.Currency) || item.Count <= 0 || utility.StringToFloat64(item.QuoteVolume) <= 0 {
 | ||
| 			helper.DefaultRedis.DeleteString(key)
 | ||
| 			deleteSymbol = append(deleteSymbol, item.Symbol)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		val, err := sonic.Marshal(symbol)
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			log.Error("设置行情序列化报错", err)
 | ||
| 		}
 | ||
| 
 | ||
| 		tcKey := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, item.Symbol)
 | ||
| 		caches[tcKey] = string(val)
 | ||
| 		priceChange = append(priceChange, &redis.Z{
 | ||
| 			Score:  symbol.PriceChange,
 | ||
| 			Member: symbol.Coin + symbol.Currency,
 | ||
| 		})
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(caches) > 0 {
 | ||
| 		err = helper.DefaultRedis.BatchSet(&caches)
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			log.Error("批量行情保存失败", err)
 | ||
| 
 | ||
| 			return deleteSymbol, err
 | ||
| 		}
 | ||
| 
 | ||
| 		err = helper.DefaultRedis.BatchSortSet(global.UFUTURES_PRICE_CHANGE, priceChange)
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			log.Error("批量涨跌幅保存失败", err)
 | ||
| 
 | ||
| 			return deleteSymbol, err
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return deleteSymbol, nil
 | ||
| }
 | ||
| 
 | ||
| // 获取合约 24h行情
 | ||
| func GetTicker(coin string, data *futuresdto.MarketResp) error {
 | ||
| 	key := fmt.Sprintf("%s:%sUSDT", global.TICKER_FUTURES, coin)
 | ||
| 	cache, _ := helper.DefaultRedis.GetString(key)
 | ||
| 
 | ||
| 	if cache == "" {
 | ||
| 
 | ||
| 	} else {
 | ||
| 		var trade models.TradeSet
 | ||
| 		sonic.Unmarshal([]byte(cache), &trade)
 | ||
| 
 | ||
| 		if trade.Coin != "" {
 | ||
| 			data.Coin = trade.Coin
 | ||
| 			data.HighPrice = trade.HighPrice
 | ||
| 			data.LowPrice = trade.LowPrice
 | ||
| 			data.NewPrice = trade.LastPrice
 | ||
| 			data.Ratio = utility.Float64ToString(trade.PriceChange, -1)
 | ||
| 			data.AmountDigit = trade.AmountDigit
 | ||
| 			data.DealCoin = trade.Volume
 | ||
| 			data.DealAmt = trade.QuoteVolume
 | ||
| 		}
 | ||
| 
 | ||
| 		fiKey := fmt.Sprintf("%s:%sUSDT", global.FUNDING_INFO_FUTURES, coin)
 | ||
| 		fi, _ := helper.DefaultRedis.GetString(fiKey)
 | ||
| 
 | ||
| 		if fi != "" {
 | ||
| 			var fundingInfo futuresdto.FundingInfo
 | ||
| 			sonic.Unmarshal([]byte(fi), &fundingInfo)
 | ||
| 
 | ||
| 			if fundingInfo.NextFundingTime > 0 {
 | ||
| 				data.FundRate = strconv.FormatFloat(utility.ToFloat64(fundingInfo.LastFundingRate)*100, 'f', -1, 64)
 | ||
| 				data.MarkPrice = utility.StringFloat64Cut(fundingInfo.MarkPrice, int32(trade.PriceDigit))
 | ||
| 				data.IndexPrice = utility.StringFloat64Cut(fundingInfo.IndexPrice, int32(trade.PriceDigit))
 | ||
| 
 | ||
| 				if fundingInfo.NextFundingTime > 999999999999 {
 | ||
| 					data.NextFundingSec = int(fundingInfo.NextFundingTime / 1000)
 | ||
| 				} else {
 | ||
| 					data.NextFundingSec = int(fundingInfo.NextFundingTime)
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // CheckKlineType 检查k线合法性
 | ||
| func CheckKlineType(kLineType string) bool {
 | ||
| 	for _, v := range []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"} {
 | ||
| 		if v == kLineType {
 | ||
| 			return true
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return false
 | ||
| }
 | ||
| 
 | ||
| func (e FutRestApi) OrderPlaceLoop(db *gorm.DB, params FutOrderPlace, retryCount int) error {
 | ||
| 	var err error
 | ||
| 	err = e.OrderPlace(db, params)
 | ||
| 	if err != nil {
 | ||
| 		//数量不正确
 | ||
| 		if strings.Contains(err.Error(), "LOT_SIZE") {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 
 | ||
| 		for x := 1; x <= retryCount; x++ {
 | ||
| 			err = e.OrderPlace(db, params)
 | ||
| 			if err == nil || strings.Contains(err.Error(), "LOT_SIZE") {
 | ||
| 				break
 | ||
| 			}
 | ||
| 			time.Sleep(200 * time.Millisecond)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // 循环平仓
 | ||
| func (e FutRestApi) ClosePositionLoop(symbol string, orderSn string, quantity decimal.Decimal, side string, positionSide string,
 | ||
| 	apiUserInfo DbModels.LineApiUser, orderType string, rate string, price decimal.Decimal, retryCount int) error {
 | ||
| 	var err error
 | ||
| 	err = e.ClosePosition(symbol, orderSn, quantity, side, positionSide, apiUserInfo, orderType, rate, price)
 | ||
| 	if err != nil {
 | ||
| 		//数量不正确
 | ||
| 		if strings.Contains(err.Error(), "LOT_SIZE") {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 
 | ||
| 		for x := 1; x <= retryCount; x++ {
 | ||
| 			err = e.ClosePosition(symbol, orderSn, quantity, side, positionSide, apiUserInfo, orderType, rate, price)
 | ||
| 			if err == nil || strings.Contains(err.Error(), "LOT_SIZE") {
 | ||
| 				break
 | ||
| 			}
 | ||
| 			time.Sleep(200 * time.Millisecond)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // OrderPlace 合约下单
 | ||
| func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error {
 | ||
| 	if orm == nil {
 | ||
| 		return errors.New("数据库实例为空")
 | ||
| 	}
 | ||
| 	err2 := params.CheckParams()
 | ||
| 	if err2 != nil {
 | ||
| 		return err2
 | ||
| 	}
 | ||
| 	paramsMaps := map[string]string{
 | ||
| 		"symbol":           params.Symbol,
 | ||
| 		"side":             params.Side,
 | ||
| 		"quantity":         params.Quantity.String(),
 | ||
| 		"type":             strings.ToUpper(params.OrderType),
 | ||
| 		"newClientOrderId": params.NewClientOrderId,
 | ||
| 	}
 | ||
| 	if strings.ToUpper(params.OrderType) != "MARKET" { //不是市价
 | ||
| 		if strings.ToUpper(params.OrderType) == "LIMIT" {
 | ||
| 			paramsMaps["price"] = params.Price.String()
 | ||
| 			paramsMaps["timeInForce"] = "GTC"
 | ||
| 		}
 | ||
| 		if strings.ToUpper(params.OrderType) == "TAKE_PROFIT_MARKET" {
 | ||
| 			paramsMaps["timeInForce"] = "GTC"
 | ||
| 			paramsMaps["stopprice"] = params.Profit.String()
 | ||
| 			paramsMaps["workingType"] = "MARK_PRICE"
 | ||
| 		}
 | ||
| 
 | ||
| 		if strings.ToUpper(params.OrderType) == "TAKE_PROFIT" {
 | ||
| 			paramsMaps["timeInForce"] = "GTC"
 | ||
| 			paramsMaps["price"] = params.Price.String()
 | ||
| 			paramsMaps["stopprice"] = params.Profit.String()
 | ||
| 			// paramsMaps["workingType"] = "MARK_PRICE"
 | ||
| 		}
 | ||
| 
 | ||
| 		if strings.ToUpper(params.OrderType) == "STOP_MARKET" {
 | ||
| 			paramsMaps["stopprice"] = params.StopPrice.String()
 | ||
| 			paramsMaps["workingType"] = "MARK_PRICE"
 | ||
| 			paramsMaps["timeInForce"] = "GTC"
 | ||
| 		}
 | ||
| 
 | ||
| 		if strings.ToUpper(params.OrderType) == "STOP" {
 | ||
| 			paramsMaps["price"] = params.Price.String()
 | ||
| 			paramsMaps["stopprice"] = params.StopPrice.String()
 | ||
| 			paramsMaps["workingType"] = "MARK_PRICE"
 | ||
| 			paramsMaps["timeInForce"] = "GTC"
 | ||
| 		}
 | ||
| 	}
 | ||
| 	if strings.ToUpper(params.OrderType) != "STOP_MARKET" && strings.ToUpper(params.OrderType) != "TAKE_PROFIT_MARKET" && strings.ToUpper(params.OrderType) != "STOP" && strings.ToUpper(params.OrderType) != "TAKE_PROFIT" {
 | ||
| 		if strings.ToUpper(params.Side) == "BUY" {
 | ||
| 			paramsMaps["positionSide"] = "LONG"
 | ||
| 		} else {
 | ||
| 			paramsMaps["positionSide"] = "SHORT"
 | ||
| 		}
 | ||
| 	} else {
 | ||
| 		if strings.ToUpper(params.Side) == "BUY" {
 | ||
| 			paramsMaps["positionSide"] = "SHORT"
 | ||
| 		} else {
 | ||
| 			paramsMaps["positionSide"] = "LONG"
 | ||
| 		}
 | ||
| 	}
 | ||
| 	apiUserInfo, err := GetApiInfo(params.ApiId)
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	_, statusCode, err := client.SendFuturesRequestAuth("/fapi/v1/order", "POST", paramsMaps)
 | ||
| 	if err != nil {
 | ||
| 		var dataMap map[string]interface{}
 | ||
| 
 | ||
| 		if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s statusCode:%v 下单失败:%s", apiUserInfo.Id, params.Symbol, statusCode, err.Error())
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			paramsVal, _ := sonic.MarshalString(¶msMaps)
 | ||
| 			log.Error("下单失败 参数:", paramsVal)
 | ||
| 			errContent := FutErrorMaps[code.(float64)]
 | ||
| 
 | ||
| 			if errContent == "" {
 | ||
| 				errContent = err.Error()
 | ||
| 			}
 | ||
| 
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, errContent)
 | ||
| 		}
 | ||
| 		if strings.Contains(err.Error(), "Margin is insufficient.") {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, FutErrorMaps[-2019])
 | ||
| 		}
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, err.Error())
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 获取合约 持仓价格、数量
 | ||
| // symbol:交易对
 | ||
| // side:方向
 | ||
| // holdeData:持仓数据
 | ||
| func (e FutRestApi) GetHoldeData(apiInfo *DbModels.LineApiUser, symbol, side string, holdeData *HoldeData) error {
 | ||
| 	var holdes []PositionRisk
 | ||
| 	var err error
 | ||
| 
 | ||
| 	for x := 0; x < 3; x++ {
 | ||
| 		holdes, err = getSymbolHolde(e, apiInfo, symbol, side, holdeData)
 | ||
| 		if err != nil {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 
 | ||
| 		if len(holdes) == 0 {
 | ||
| 			logger.Error("获取持仓信息,未查询到持仓信息")
 | ||
| 			time.Sleep(time.Second * 1)
 | ||
| 		} else {
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	//side=SELL&positionSide=LONG是平多,
 | ||
| 	//side=SELL&positionSide=SHORT是开空,
 | ||
| 	for _, item := range holdes {
 | ||
| 		positionAmount, _ := decimal.NewFromString(item.PositionAmt)
 | ||
| 		if side == "BUY" && (item.PositionSide == "BOTH" || item.PositionSide == "LONG") { //多
 | ||
| 			holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice)
 | ||
| 			holdeData.TotalQuantity = positionAmount.Abs()
 | ||
| 		} else if side == "SELL" && (item.PositionSide == "BOTH" || item.PositionSide == "SHORT") { //空
 | ||
| 			holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice)
 | ||
| 			holdeData.TotalQuantity = positionAmount.Abs()
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	if holdeData.AveragePrice.Cmp(decimal.Zero) == 0 {
 | ||
| 		holdesVal, _ := sonic.MarshalString(&holdes)
 | ||
| 		log.Error("均价错误 symbol:", symbol, " 数据:", holdesVal)
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 获取代币持仓信息
 | ||
| func getSymbolHolde(e FutRestApi, apiInfo *DbModels.LineApiUser, symbol string, side string, holdeData *HoldeData) ([]PositionRisk, error) {
 | ||
| 	holdes, err := e.GetPositionV3(apiInfo, symbol)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		log.Error("订单回调-获取合约持仓信息失败:", err)
 | ||
| 
 | ||
| 		if strings.Contains(err.Error(), " EOF") || strings.Contains(err.Error(), "无效的API密钥,IP或操作权限") {
 | ||
| 			return nil, e.GetHoldeData(apiInfo, symbol, side, holdeData)
 | ||
| 		} else {
 | ||
| 			return nil, err
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return holdes, nil
 | ||
| }
 | ||
| 
 | ||
| func (e FutRestApi) GetPositionV3(apiUserInfo *DbModels.LineApiUser, symbol string) ([]PositionRisk, error) {
 | ||
| 	client := GetClient(apiUserInfo)
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":     symbol,
 | ||
| 		"recvWindow": "5000",
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, _, err := client.SendFuturesRequestAuth("/fapi/v3/positionRisk", "GET", params)
 | ||
| 	if err != nil {
 | ||
| 		var dataMap map[string]interface{}
 | ||
| 		if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil {
 | ||
| 			return []PositionRisk{}, fmt.Errorf("api_id:%d 交易对:%s 获取仓位:%s", apiUserInfo.Id, symbol, err.Error())
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			errContent := FutErrorMaps[code.(float64)]
 | ||
| 
 | ||
| 			if errContent == "" {
 | ||
| 				errContent = err.Error()
 | ||
| 			}
 | ||
| 
 | ||
| 			return []PositionRisk{}, fmt.Errorf("api_id:%d 交易对:%s 获取仓位:%s", apiUserInfo.Id, symbol, errContent)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	risks := make([]PositionRisk, 0)
 | ||
| 	err = sonic.Unmarshal(resp, &risks)
 | ||
| 	if err != nil {
 | ||
| 		return []PositionRisk{}, fmt.Errorf("api_id:%d 交易对:%s 解析用户持仓信息失败:%s", apiUserInfo.Id, symbol, err.Error())
 | ||
| 	}
 | ||
| 	return risks, nil
 | ||
| }
 | ||
| 
 | ||
| // ClosePosition 合约平仓
 | ||
| // symbol 交易对
 | ||
| // orderSn 系统订单号
 | ||
| // quantity 平仓数量
 | ||
| // side 仓位方向 做多==BUY 做空 == SELL
 | ||
| // apiUserInfo 用户信息
 | ||
| // orderType 平仓是限价平 还是市价平 MARKET = 市价 LIMIT = 限价
 | ||
| // rate 限价平的价格比例 没有除100 的百分比
 | ||
| func (e FutRestApi) ClosePosition(symbol string, orderSn string, quantity decimal.Decimal, side string, positionSide string,
 | ||
| 	apiUserInfo DbModels.LineApiUser, orderType string, rate string, price decimal.Decimal) error {
 | ||
| 	endpoint := "/fapi/v1/order"
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":           symbol,
 | ||
| 		"side":             side,
 | ||
| 		"positionSide":     positionSide,
 | ||
| 		"type":             orderType,
 | ||
| 		"quantity":         quantity.String(),
 | ||
| 		"newClientOrderId": orderSn,
 | ||
| 	}
 | ||
| 
 | ||
| 	if orderType == "LIMIT" {
 | ||
| 		key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, symbol)
 | ||
| 		tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key)
 | ||
| 		rateFloat, _ := decimal.NewFromString(rate)
 | ||
| 		if rateFloat.GreaterThanOrEqual(decimal.Zero) {
 | ||
| 			if side == "SELL" { //仓位是空 平空的话
 | ||
| 				price = price.Mul(decimal.NewFromInt(1).Add(rateFloat)).Truncate(int32(tradeSet.PriceDigit))
 | ||
| 			} else {
 | ||
| 				price = price.Mul(decimal.NewFromInt(1).Sub(rateFloat)).Truncate(int32(tradeSet.PriceDigit))
 | ||
| 			}
 | ||
| 			params["price"] = price.String()
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	if orderType == "LIMIT" {
 | ||
| 		params["timeInForce"] = "GTC"
 | ||
| 	}
 | ||
| 
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 
 | ||
| 	resp, _, err := client.SendFuturesRequestAuth(endpoint, "POST", params)
 | ||
| 	if err != nil {
 | ||
| 		var dataMap map[string]interface{}
 | ||
| 
 | ||
| 		if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error())
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			errContent := FutErrorMaps[code.(float64)]
 | ||
| 
 | ||
| 			if errContent == "" {
 | ||
| 				errContent = err.Error()
 | ||
| 			}
 | ||
| 
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, errContent)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	var orderResp FutOrderResp
 | ||
| 	err = sonic.Unmarshal(resp, &orderResp)
 | ||
| 	if err != nil {
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error())
 | ||
| 	}
 | ||
| 	if orderResp.Symbol == "" {
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:未找到订单信息", apiUserInfo.Id, symbol)
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 带重试机制的取消订单
 | ||
| func (e FutRestApi) CancelFutOrderRetry(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderId string) error {
 | ||
| 	opts := retryhelper.DefaultRetryOptions()
 | ||
| 
 | ||
| 	return retryhelper.Retry(func() error {
 | ||
| 		return e.CancelFutOrder(apiUserInfo, symbol, newClientOrderId)
 | ||
| 	}, opts)
 | ||
| }
 | ||
| 
 | ||
| // CancelFutOrder 通过单个订单号取消合约委托
 | ||
| // symbol 交易对
 | ||
| // newClientOrderId 系统自定义订单号
 | ||
| func (e FutRestApi) CancelFutOrder(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderId string) error {
 | ||
| 	endpoint := "/fapi/v1/order"
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":            symbol,           //交易对
 | ||
| 		"origClientOrderId": newClientOrderId, //用户自定义订单号
 | ||
| 	}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	_, _, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params)
 | ||
| 	if err != nil {
 | ||
| 		var dataMap map[string]interface{}
 | ||
| 		if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销合约委托出错:%s", apiUserInfo.Id, symbol, err.Error())
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			errContent := FutErrorMaps[code.(float64)]
 | ||
| 
 | ||
| 			if errContent == "" {
 | ||
| 				errContent = err.Error()
 | ||
| 			}
 | ||
| 
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销合约委托出错:%s", apiUserInfo.Id, symbol, errContent)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // CancelAllFutOrder 通过交易对取消合约委托
 | ||
| // symbol 交易对
 | ||
| func (e FutRestApi) CancelAllFutOrder(apiUserInfo DbModels.LineApiUser, symbol string) error {
 | ||
| 	endpoint := "/fapi/v1/allOpenOrders"
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":     symbol, //交易对
 | ||
| 		"recvWindow": "5000",
 | ||
| 	}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	_, _, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params)
 | ||
| 	if err != nil {
 | ||
| 		var dataMap map[string]interface{}
 | ||
| 		if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销全部合约委托出错:%s", apiUserInfo.Id, symbol, err.Error())
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			errContent := FutErrorMaps[code.(float64)]
 | ||
| 
 | ||
| 			if errContent == "" {
 | ||
| 				errContent = err.Error()
 | ||
| 			}
 | ||
| 
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销全部合约委托出错:%s", apiUserInfo.Id, symbol, errContent)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // CancelBatchFutOrder 批量撤销订单
 | ||
| // symbol 交易对
 | ||
| // newClientOrderIdList 系统自定义的订单号, 最多支持10个订单
 | ||
| func (e FutRestApi) CancelBatchFutOrder(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderIdList []string) error {
 | ||
| 	if len(newClientOrderIdList) > 10 {
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, "最多支持10个订单")
 | ||
| 	}
 | ||
| 	endpoint := "/fapi/v1/batchOrders"
 | ||
| 	marshal, _ := sonic.Marshal(newClientOrderIdList)
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":                symbol, //交易对
 | ||
| 		"origClientOrderIdList": string(marshal),
 | ||
| 	}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	_, code, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params)
 | ||
| 	if err != nil {
 | ||
| 		log.Error("取消合约委托失败 参数:", params)
 | ||
| 		log.Error("取消合约委托失败 code:", code)
 | ||
| 		log.Error("取消合约委托失败 err:", err)
 | ||
| 		var dataMap map[string]interface{}
 | ||
| 		if err.Error() != "" {
 | ||
| 			if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil {
 | ||
| 				return fmt.Errorf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, err.Error())
 | ||
| 			}
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			errContent := FutErrorMaps[code.(float64)]
 | ||
| 
 | ||
| 			if errContent == "" {
 | ||
| 				errContent = err.Error()
 | ||
| 			}
 | ||
| 
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, errContent)
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // CalcSymbolExchangeAmt 计算兑换成USDT后的交易对数量
 | ||
| // symbol 需要兑换的交易对 例如 ETHBTC
 | ||
| // quoteSymbol 计价货币  ETHBTC -> 计价货币就是BTC
 | ||
| // totalMoney 兑换的总金额
 | ||
| func (e FutRestApi) CalcSymbolExchangeAmt(symbol string, quoteSymbol string, totalMoney decimal.Decimal) decimal.Decimal {
 | ||
| 	tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val()
 | ||
| 	tickerSymbolMaps := make([]dto.Ticker, 0)
 | ||
| 	sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps)
 | ||
| 
 | ||
| 	targetSymbol := strings.Replace(symbol, quoteSymbol, "USDT", 1) //ETHBTC -》 ETHUSDT
 | ||
| 	key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, targetSymbol)
 | ||
| 	tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key)
 | ||
| 	var targetPrice decimal.Decimal
 | ||
| 	quantity := decimal.Zero
 | ||
| 	for _, symbolMap := range tickerSymbolMaps {
 | ||
| 		if symbolMap.Symbol == strings.ToUpper(targetSymbol) {
 | ||
| 			targetPrice, _ = decimal.NewFromString(symbolMap.Price)
 | ||
| 			quantity = totalMoney.Div(targetPrice).Truncate(int32(tradeSet.AmountDigit))
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return quantity
 | ||
| }
 | ||
| 
 | ||
| // GetFutSymbolLastPrice 获取现货交易对最新价格
 | ||
| func (e FutRestApi) GetFutSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) {
 | ||
| 	key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, targetSymbol)
 | ||
| 	tickerSymbol, _ := helper.DefaultRedis.GetString(key)
 | ||
| 
 | ||
| 	if tickerSymbol != "" {
 | ||
| 		tradeSet := models.TradeSet{}
 | ||
| 
 | ||
| 		sonic.Unmarshal([]byte(tickerSymbol), &tradeSet)
 | ||
| 
 | ||
| 		if tradeSet.LastPrice != "" {
 | ||
| 			lastPrice = utility.StrToDecimal(tradeSet.LastPrice).Truncate(int32(tradeSet.PriceDigit))
 | ||
| 		}
 | ||
| 	}
 | ||
| 	// tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val()
 | ||
| 	// tickerSymbolMaps := make([]dto.Ticker, 0)
 | ||
| 	// sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps)
 | ||
| 	// for _, symbolMap := range tickerSymbolMaps {
 | ||
| 	// 	if symbolMap.Symbol == strings.ToUpper(targetSymbol) {
 | ||
| 	// 		lastPrice = utility.StringToDecimal(symbolMap.Price)
 | ||
| 	// 	}
 | ||
| 	// }
 | ||
| 	return lastPrice
 | ||
| }
 | ||
| 
 | ||
| func (e FutRestApi) GetOrderByOrderSn(symbol, orderSn string, apiUserInfo DbModels.LineApiUser) (order binancedto.BinanceFutureOrder, err error) {
 | ||
| 	result := binancedto.BinanceFutureOrder{}
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":            symbol,
 | ||
| 		"origClientOrderId": orderSn,
 | ||
| 	}
 | ||
| 
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 
 | ||
| 	body, code, err := client.SendFuturesAuth("/fapi/v1/order", "GET", params)
 | ||
| 	if err != nil || code != 200 {
 | ||
| 		log.Error("查询合约委托 参数:", params)
 | ||
| 		log.Error("查询合约委托失败 code:", code)
 | ||
| 		log.Error("查询合约委托失败 err:", err)
 | ||
| 		dataMap := make(map[string]interface{})
 | ||
| 		if err.Error() != "" {
 | ||
| 			if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
 | ||
| 				return result, fmt.Errorf("api_id:%d 交易对:%s 查询订单失败:%+v", apiUserInfo.Id, symbol, err.Error())
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			return result, fmt.Errorf("api_id:%d 交易对:%s 查询订单失败:%s", apiUserInfo.Id, symbol, ErrorMaps[code.(float64)])
 | ||
| 		}
 | ||
| 		if strings.Contains(err.Error(), "Unknown order sent.") {
 | ||
| 			return result, fmt.Errorf("api_id:%d 交易对:%s 查询订单失败:%+v", apiUserInfo.Id, symbol, ErrorMaps[-2011])
 | ||
| 		}
 | ||
| 		return result, fmt.Errorf("api_id:%d 交易对:%s 查询订单失败:%+v", apiUserInfo.Id, symbol, err.Error())
 | ||
| 	}
 | ||
| 
 | ||
| 	sonic.Unmarshal(body, &result)
 | ||
| 
 | ||
| 	if result.OrderID == 0 {
 | ||
| 		return result, fmt.Errorf("api_id:%d 交易对:%s 查询订单失败:%+v", apiUserInfo.Id, symbol, "订单不存在")
 | ||
| 	}
 | ||
| 
 | ||
| 	return result, nil
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 查询合约委托
 | ||
| */
 | ||
| // 根据订单号获取订单信息,如果获取失败,则进行重试
 | ||
| func (e FutRestApi) GetOrderByOrderSnLoop(symbol, ordersn string, apiUserInfo DbModels.LineApiUser, retryCount int) (order binancedto.BinanceFutureOrder, err error) {
 | ||
| 	result, err := e.GetOrderByOrderSn(symbol, ordersn, apiUserInfo)
 | ||
| 	// 如果获取失败,则进行重试
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		// 调用GetOrderByOrderSn方法获取订单信息
 | ||
| 		for x := 1; x < retryCount; x++ {
 | ||
| 			// 如果获取成功,则跳出循环
 | ||
| 			result, err = e.GetOrderByOrderSn(symbol, ordersn, apiUserInfo)
 | ||
| 			if err == nil {
 | ||
| 				break
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	// 返回订单信息和错误信息
 | ||
| 
 | ||
| 	return result, err
 | ||
| }
 | ||
| 
 | ||
| // 获取合约U资产
 | ||
| func GetFuturesUProperty(apiUserInfo DbModels.LineApiUser, data *dto.LineUserPropertyResp) error {
 | ||
| 	endpoint := "/fapi/v3/balance"
 | ||
| 	params := map[string]string{
 | ||
| 		"recvWindow": "5000",
 | ||
| 	}
 | ||
| 
 | ||
| 	balanceResp := make([]binancedto.BinanceFutureBalance, 0)
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	body, code, err := client.SendFuturesAuth(endpoint, "GET", params)
 | ||
| 
 | ||
| 	if err != nil || code != 200 {
 | ||
| 		log.Error("查询合约资产 参数:", params)
 | ||
| 		log.Error("查询合约资产 code:", code)
 | ||
| 		log.Error("查询合约资产 err:", err)
 | ||
| 		dataMap := make(map[string]interface{})
 | ||
| 		if err.Error() != "" {
 | ||
| 			if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
 | ||
| 				return fmt.Errorf("api_id:%d 查询资产失败:%+v", apiUserInfo.Id, err.Error())
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			return fmt.Errorf("api_id:%d 查询资产失败:%s", apiUserInfo.Id, ErrorMaps[code.(float64)])
 | ||
| 		}
 | ||
| 		if strings.Contains(err.Error(), "Unknown order sent.") {
 | ||
| 			return fmt.Errorf("api_id:%d  查询资产失败:%+v", apiUserInfo.Id, ErrorMaps[-2011])
 | ||
| 		}
 | ||
| 		return fmt.Errorf("api_id:%d 查询资产失败:%+v", apiUserInfo.Id, err.Error())
 | ||
| 	}
 | ||
| 
 | ||
| 	sonic.Unmarshal(body, &balanceResp)
 | ||
| 
 | ||
| 	for _, v := range balanceResp {
 | ||
| 		if v.Asset == "USDT" {
 | ||
| 			free := utility.StrToDecimal(v.AvailableBalance)
 | ||
| 
 | ||
| 			data.FuturesFreeAmount = free
 | ||
| 			data.FuturesTotalAmount = utility.StrToDecimal(v.Balance)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 |