698 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			698 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package binanceservice
 | ||
| 
 | ||
| import (
 | ||
| 	"errors"
 | ||
| 	"fmt"
 | ||
| 	DbModels "go-admin/app/admin/models"
 | ||
| 	"go-admin/app/admin/service/dto"
 | ||
| 	"go-admin/common/const/rediskey"
 | ||
| 	commondto "go-admin/common/dto"
 | ||
| 	"go-admin/common/global"
 | ||
| 	"go-admin/common/helper"
 | ||
| 	"go-admin/models"
 | ||
| 	"go-admin/models/binancedto"
 | ||
| 	"go-admin/models/spot"
 | ||
| 	"go-admin/pkg/httputils"
 | ||
| 	"go-admin/pkg/utility"
 | ||
| 	"strings"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/shopspring/decimal"
 | ||
| 	"gorm.io/gorm"
 | ||
| 
 | ||
| 	"github.com/bytedance/sonic"
 | ||
| 	log "github.com/go-admin-team/go-admin-core/logger"
 | ||
| )
 | ||
| 
 | ||
| const (
 | ||
| 	binanceRestApi = "https://api.binance.com"
 | ||
| )
 | ||
| 
 | ||
| var ErrorMaps = map[float64]string{
 | ||
| 	-2021: "订单已拒绝。请调整触发价并重新下订单。 对于买入/做多,止盈订单触发价应低于市场价,止损订单的触发价应高于市场价。卖出/做空则与之相反",
 | ||
| 	-4164: "下单失败。少于最小下单金额",
 | ||
| 	-4061: "持仓方向需要设置为单向持仓。",
 | ||
| 	-2019: "保证金不足",
 | ||
| 	-1111: "金额设置错误。精度错误",
 | ||
| 	-1021: "请求的时间戳在recvWindow之外",
 | ||
| 	-2011: "该交易对没有订单",
 | ||
| 	// -2010: "账户余额不足",
 | ||
| }
 | ||
| 
 | ||
| type SpotRestApi struct {
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取 现货交易-规范信息
 | ||
| 
 | ||
|   - return @info 规范信息
 | ||
|   - return @err 错误信息
 | ||
| */
 | ||
| func (e SpotRestApi) GetExchangeInfo() (symbols []spot.Symbol, err error) {
 | ||
| 	url := fmt.Sprintf("%s%s?permissions=SPOT", binanceRestApi, "/api/v3/exchangeInfo")
 | ||
| 	mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{})
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(mapData) == 0 {
 | ||
| 		err = errors.New("获取现货交易-规范信息数量为空")
 | ||
| 		return
 | ||
| 	}
 | ||
| 
 | ||
| 	var info spot.ExchangeInfo
 | ||
| 	err = sonic.Unmarshal(mapData, &info)
 | ||
| 
 | ||
| 	if err == nil {
 | ||
| 		return info.Symbols, nil
 | ||
| 	}
 | ||
| 
 | ||
| 	return
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取现货24h行情变更
 | ||
| */
 | ||
| func (e SpotRestApi) GetSpotTicker24h(tradeSet *map[string]models.TradeSet) (deleteSymbols []string, err error) {
 | ||
| 	tickerApi := fmt.Sprintf("%s%s", binanceRestApi, "/api/v3/ticker/24hr")
 | ||
| 	mapData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerApi, "", map[string]string{})
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return []string{}, err
 | ||
| 	}
 | ||
| 	deleteSymbols = make([]string, 0)
 | ||
| 	if len(mapData) == 0 {
 | ||
| 		return deleteSymbols, errors.New("获取交易对失败,或数量为空")
 | ||
| 	}
 | ||
| 	tickers := make([]spot.SpotTicker24h, 0)
 | ||
| 	err = sonic.Unmarshal([]byte(mapData), &tickers)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		log.Error("反序列化json失败", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, item := range tickers {
 | ||
| 		key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, item.Symbol)
 | ||
| 		symbol, exits := (*tradeSet)[item.Symbol]
 | ||
| 
 | ||
| 		if !exits {
 | ||
| 			helper.DefaultRedis.DeleteString(key)
 | ||
| 			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
 | ||
| 
 | ||
| 		val, err := sonic.Marshal(symbol)
 | ||
| 
 | ||
| 		if !strings.HasSuffix(item.Symbol, symbol.Currency) || item.Count <= 0 || utility.StringToFloat64(item.QuoteVolume) <= 0 {
 | ||
| 			helper.DefaultRedis.DeleteString(key)
 | ||
| 			deleteSymbols = append(deleteSymbols, item.Symbol)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			log.Error("序列化失败", item.Symbol)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		err = helper.DefaultRedis.SetString(key, string(val))
 | ||
| 
 | ||
| 		if err != nil {
 | ||
| 			log.Error("缓存交易对失败|", item.Symbol, err)
 | ||
| 		}
 | ||
| 
 | ||
| 		helper.DefaultRedis.AddSortSet(global.COIN_PRICE_CHANGE, symbol.PriceChange, symbol.Coin)
 | ||
| 	}
 | ||
| 	return deleteSymbols, nil
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取单个交易对24h行情
 | ||
| 
 | ||
|   - @symbol 交易对
 | ||
|   - @data 结果
 | ||
| */
 | ||
| func (e SpotRestApi) GetSpotTicker24(symbol string, data *models.Ticker24, tradeSet *models.TradeSet) error {
 | ||
| 	key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol)
 | ||
| 	val, err := helper.DefaultRedis.GetString(key)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	err = sonic.Unmarshal([]byte(val), tradeSet)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	if tradeSet.Coin != "" {
 | ||
| 		data.HighPrice = tradeSet.HighPrice
 | ||
| 		data.ChangePercent = fmt.Sprintf("%g", tradeSet.PriceChange)
 | ||
| 		data.LastPrice = tradeSet.LastPrice
 | ||
| 		data.LowPrice = tradeSet.LowPrice
 | ||
| 		data.OpenPrice = fmt.Sprintf("%g", tradeSet.OpenPrice)
 | ||
| 		data.QuoteVolume = tradeSet.QuoteVolume
 | ||
| 		data.Volume = tradeSet.Volume
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| type Ticker struct {
 | ||
| 	Symbol string `json:"symbol"`
 | ||
| 	Price  string `json:"price"`
 | ||
| }
 | ||
| 
 | ||
| func (e SpotRestApi) Ticker() {
 | ||
| 	tickerApi := fmt.Sprintf("%s%s", binanceRestApi, "/api/v3/ticker/price")
 | ||
| 	mapData, _ := httputils.NewHttpRequestWithFasthttp("GET", tickerApi, "", map[string]string{})
 | ||
| 	//sonic.Unmarshal(mapData, &tickerData)
 | ||
| 	helper.DefaultRedis.SetString(rediskey.SpotSymbolTicker, string(mapData))
 | ||
| }
 | ||
| 
 | ||
| // 循环下单
 | ||
| func (e SpotRestApi) OrderPlaceLoop(db *gorm.DB, params OrderPlacementService, 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
 | ||
| 			}
 | ||
| 
 | ||
| 			// if strings.Contains(err.Error(), "余额不足") {
 | ||
| 			// 	apiUserInfo, _ := GetApiInfo(params.ApiId)
 | ||
| 			// 	tradeset, _ := GetTradeSet(params.Symbol, 0)
 | ||
| 			// 	num, _ := getSpotPositionNum(apiUserInfo, &DbModels.LinePreOrder{Symbol: params.Symbol, QuoteSymbol: "USDT", OrderSn: params.NewClientOrderId}, tradeset)
 | ||
| 			// 	log.Info(" 循环下单 余额:%v", num)
 | ||
| 			// }
 | ||
| 			time.Sleep(500 * time.Millisecond)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // OrderPlace 现货下单
 | ||
| func (e SpotRestApi) OrderPlace(orm *gorm.DB, params OrderPlacementService) 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":             params.Type,
 | ||
| 		"newClientOrderId": params.NewClientOrderId,
 | ||
| 	}
 | ||
| 	if strings.ToUpper(params.Type) != "MARKET" { //市价
 | ||
| 		paramsMaps["price"] = params.Price.String()
 | ||
| 		paramsMaps["timeInForce"] = "GTC"
 | ||
| 
 | ||
| 		if strings.ToUpper(params.Type) == "TAKE_PROFIT_LIMIT" || strings.ToUpper(params.Type) == "STOP_LOSS_LIMIT" {
 | ||
| 			paramsMaps["stopPrice"] = params.StopPrice.String()
 | ||
| 		}
 | ||
| 	}
 | ||
| 	apiUserInfo, err := GetApiInfo(params.ApiId)
 | ||
| 	if apiUserInfo.Id == 0 {
 | ||
| 		log.Errorf("api用户出错 err: %+v", err)
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	resp, _, err := client.SendSpotAuth("/api/v3/order", "POST", paramsMaps)
 | ||
| 	if err != nil {
 | ||
| 		dataMap := make(map[string]interface{})
 | ||
| 
 | ||
| 		if err.Error() != "" {
 | ||
| 			if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
 | ||
| 				return fmt.Errorf("api_id:%d 交易对:%s 下订单失败:%+v", apiUserInfo.Id, params.Symbol, err.Error())
 | ||
| 			}
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			errContent := ErrorMaps[code.(float64)]
 | ||
| 			paramsVal, _ := sonic.MarshalString(paramsMaps)
 | ||
| 
 | ||
| 			log.Error("api_id:", utility.IntToString(apiUserInfo.Id), " 交易对:", params.Symbol, " 下单参数:", paramsVal)
 | ||
| 
 | ||
| 			if errContent == "" {
 | ||
| 				errContent, _ = dataMap["msg"].(string)
 | ||
| 			}
 | ||
| 
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 下订单失败:%s", apiUserInfo.Id, params.Symbol, errContent)
 | ||
| 		}
 | ||
| 		if strings.Contains(err.Error(), "Unknown order sent.") {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%+v", apiUserInfo.Id, params.Symbol, ErrorMaps[-2011])
 | ||
| 		}
 | ||
| 
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%v", apiUserInfo.Id, params.Symbol, err)
 | ||
| 	}
 | ||
| 	var dataMap map[string]interface{}
 | ||
| 	if err := sonic.Unmarshal(resp, &dataMap); err != nil {
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%+v", apiUserInfo.Id, params.Symbol, err.Error())
 | ||
| 	}
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 循环取消
 | ||
| func (e SpotRestApi) CancelOpenOrdersLoop(orm *gorm.DB, req CancelOpenOrdersReq, retryCount int) error {
 | ||
| 	err := e.CancelOpenOrders(orm, req)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		for x := 1; x < retryCount; x++ {
 | ||
| 			err = e.CancelOpenOrders(orm, req)
 | ||
| 			if err == nil {
 | ||
| 				break
 | ||
| 			}
 | ||
| 
 | ||
| 			time.Sleep(time.Duration(x) * 200 * time.Millisecond)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return err
 | ||
| }
 | ||
| 
 | ||
| // CancelOpenOrders 撤销单一交易对下所有挂单 包括了来自订单列表的挂单
 | ||
| func (e SpotRestApi) CancelOpenOrders(orm *gorm.DB, req CancelOpenOrdersReq) error {
 | ||
| 	if orm == nil {
 | ||
| 		return errors.New("数据库实例为空")
 | ||
| 	}
 | ||
| 	err := req.CheckParams()
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol": req.Symbol,
 | ||
| 	}
 | ||
| 	apiUserInfo, err := GetApiInfo(req.ApiId)
 | ||
| 
 | ||
| 	if apiUserInfo.Id == 0 {
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s api用户出错:%+v", apiUserInfo.Id, req.Symbol, err)
 | ||
| 	}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	_, _, err = client.SendSpotAuth("/api/v3/openOrders", "DELETE", params)
 | ||
| 	if err != nil {
 | ||
| 		dataMap := make(map[string]interface{})
 | ||
| 
 | ||
| 		if err.Error() != "" {
 | ||
| 			if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil {
 | ||
| 				return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, err.Error())
 | ||
| 			}
 | ||
| 		}
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%s", apiUserInfo.Id, req.Symbol, ErrorMaps[code.(float64)])
 | ||
| 		}
 | ||
| 		if strings.Contains(err.Error(), "Unknown order sent.") {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, ErrorMaps[-2011])
 | ||
| 		}
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, err.Error())
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // CancelOpenOrderByOrderSn 通过单一订单号取消委托
 | ||
| func (e SpotRestApi) CancelOpenOrderByOrderSn(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderId string) error {
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":            symbol,
 | ||
| 		"origClientOrderId": newClientOrderId,
 | ||
| 		"recvWindow":        "10000",
 | ||
| 	}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	_, code, err := client.SendSpotAuth("/api/v3/order", "DELETE", 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 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, err.Error())
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		code, ok := dataMap["code"]
 | ||
| 		if ok {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%s", apiUserInfo.Id, symbol, ErrorMaps[code.(float64)])
 | ||
| 		}
 | ||
| 		if strings.Contains(err.Error(), "Unknown order sent.") {
 | ||
| 			return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, ErrorMaps[-2011])
 | ||
| 		}
 | ||
| 		return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, err.Error())
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // ClosePosition 平仓
 | ||
| // symbol 交易对
 | ||
| // orderSn 平仓单号
 | ||
| // quantity 平仓数量
 | ||
| // side 原始仓位方向
 | ||
| // apiUserInfo 用户信息
 | ||
| // orderType 平仓类型 限价(LIMIT) 市价()
 | ||
| func (e SpotRestApi) ClosePosition(symbol string, orderSn string, quantity decimal.Decimal, side string,
 | ||
| 	apiUserInfo DbModels.LineApiUser, orderType string, rate string, price decimal.Decimal) error {
 | ||
| 	endpoint := "/api/v3/order "
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":           symbol,
 | ||
| 		"type":             orderType,
 | ||
| 		"quantity":         quantity.String(),
 | ||
| 		"newClientOrderId": orderSn,
 | ||
| 	}
 | ||
| 	if side == "SELL" {
 | ||
| 		params["side"] = "BUY"
 | ||
| 	} else {
 | ||
| 		params["side"] = "SELL"
 | ||
| 	}
 | ||
| 
 | ||
| 	if orderType == "LIMIT" {
 | ||
| 		key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol)
 | ||
| 		tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key)
 | ||
| 		rateFloat, _ := decimal.NewFromString(rate)
 | ||
| 		if rateFloat.GreaterThan(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()
 | ||
| 		}
 | ||
| 	}
 | ||
| 	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 GetClient(apiUserInfo *DbModels.LineApiUser) *helper.BinanceClient {
 | ||
| 	var client *helper.BinanceClient
 | ||
| 	if apiUserInfo.UserPass == "" {
 | ||
| 		client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress)
 | ||
| 	} else {
 | ||
| 		client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress)
 | ||
| 	}
 | ||
| 	return client
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 获取api用户信息
 | ||
| */
 | ||
| func GetApiInfo(apiId int) (DbModels.LineApiUser, error) {
 | ||
| 	api := DbModels.LineApiUser{}
 | ||
| 	key := fmt.Sprintf(rediskey.API_USER, apiId)
 | ||
| 	val, _ := helper.DefaultRedis.GetString(key)
 | ||
| 
 | ||
| 	if val != "" {
 | ||
| 		if err := sonic.UnmarshalString(val, &api); err == nil {
 | ||
| 			return api, nil
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	db := GetDBConnection()
 | ||
| 
 | ||
| 	if err := db.Model(&api).Where("id =?", apiId).First(&api).Error; err != nil {
 | ||
| 		return api, err
 | ||
| 	}
 | ||
| 
 | ||
| 	val, _ = sonic.MarshalString(&api)
 | ||
| 
 | ||
| 	if val != "" {
 | ||
| 		helper.DefaultRedis.SetString(key, val)
 | ||
| 	}
 | ||
| 
 | ||
| 	return api, nil
 | ||
| }
 | ||
| 
 | ||
| // GetReverseApiInfo 根据apiKey获取api用户信息
 | ||
| func GetApiInfoByKey(apiKey string) DbModels.LineApiUser {
 | ||
| 	result := DbModels.LineApiUser{}
 | ||
| 	keys, _ := helper.DefaultRedis.GetAllKeysAndValues(fmt.Sprintf(rediskey.API_USER, "*"))
 | ||
| 
 | ||
| 	for _, key := range keys {
 | ||
| 		if strings.Contains(key, apiKey) {
 | ||
| 			sonic.UnmarshalString(key, &result)
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return result
 | ||
| }
 | ||
| 
 | ||
| /*
 | ||
| 根据A账户获取B账号信息
 | ||
| */
 | ||
| func GetChildApiInfo(apiId int) (DbModels.LineApiUser, error) {
 | ||
| 	var api DbModels.LineApiUser
 | ||
| 	childApiId := 0
 | ||
| 	groups := GetApiGroups()
 | ||
| 
 | ||
| 	for _, item := range groups {
 | ||
| 		if item.ApiUserId == apiId {
 | ||
| 			childApiId = item.ChildApiUserId
 | ||
| 			break
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	if childApiId > 0 {
 | ||
| 		return GetApiInfo(childApiId)
 | ||
| 	}
 | ||
| 
 | ||
| 	return api, nil
 | ||
| }
 | ||
| 
 | ||
| func GetApiGroups() []commondto.ApiGroupDto {
 | ||
| 	apiGroups := make([]commondto.ApiGroupDto, 0)
 | ||
| 	apiGroupStr, _ := helper.DefaultRedis.GetAllKeysAndValues(rediskey.ApiGroupAll)
 | ||
| 
 | ||
| 	if len(apiGroupStr) == 0 {
 | ||
| 		return apiGroups
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, item := range apiGroupStr {
 | ||
| 		apiGroup := commondto.ApiGroupDto{}
 | ||
| 		if err := sonic.UnmarshalString(item, &apiGroup); err != nil {
 | ||
| 			log.Error("groups 序列化失败", err)
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		apiGroups = append(apiGroups, apiGroup)
 | ||
| 	}
 | ||
| 
 | ||
| 	return apiGroups
 | ||
| }
 | ||
| 
 | ||
| // GetSpotSymbolLastPrice 获取现货交易对最新价格
 | ||
| func (e SpotRestApi) GetSpotSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) {
 | ||
| 	key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, targetSymbol)
 | ||
| 	ticker, _ := helper.DefaultRedis.GetString(key)
 | ||
| 
 | ||
| 	if ticker != "" {
 | ||
| 		tradeSet := models.TradeSet{}
 | ||
| 		sonic.Unmarshal([]byte(ticker), &tradeSet)
 | ||
| 
 | ||
| 		if tradeSet.LastPrice != "" {
 | ||
| 			lastPrice = utility.StrToDecimal(tradeSet.LastPrice).Truncate(int32(tradeSet.PriceDigit))
 | ||
| 		}
 | ||
| 	}
 | ||
| 	// tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val()
 | ||
| 	// tickerSymbolMaps := make([]dto.Ticker, 0)
 | ||
| 	// sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps)
 | ||
| 	// //key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, targetSymbol)
 | ||
| 	// //tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key)
 | ||
| 	// for _, symbolMap := range tickerSymbolMaps {
 | ||
| 	// 	if symbolMap.Symbol == strings.ToUpper(targetSymbol) {
 | ||
| 	// 		lastPrice = utility.StringToDecimal(symbolMap.Price)
 | ||
| 	// 	}
 | ||
| 	// }
 | ||
| 	return lastPrice
 | ||
| }
 | ||
| 
 | ||
| func (e SpotRestApi) GetOrderByOrderSn(symbol, orderSn string, apiUserInfo DbModels.LineApiUser) (order binancedto.BinanceSpotOrder, err error) {
 | ||
| 	result := binancedto.BinanceSpotOrder{}
 | ||
| 	params := map[string]string{
 | ||
| 		"symbol":            symbol,
 | ||
| 		"origClientOrderId": orderSn,
 | ||
| 	}
 | ||
| 
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 
 | ||
| 	body, code, err := client.SendSpotAuth("/api/v3/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 SpotRestApi) GetOrderByOrderSnLoop(symbol, ordersn string, apiUserInfo DbModels.LineApiUser, retryCount int) (order binancedto.BinanceSpotOrder, err error) {
 | ||
| 	result, err := e.GetOrderByOrderSn(symbol, ordersn, apiUserInfo)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		for x := 1; x < retryCount; x++ {
 | ||
| 			result, err = e.GetOrderByOrderSn(symbol, ordersn, apiUserInfo)
 | ||
| 			if err == nil {
 | ||
| 				break
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return result, err
 | ||
| }
 | ||
| 
 | ||
| // 获取现货U资产
 | ||
| func GetSpotUProperty(apiUserInfo DbModels.LineApiUser, data *dto.LineUserPropertyResp) error {
 | ||
| 	endpoint := "/api/v3/account"
 | ||
| 	params := map[string]string{
 | ||
| 		"omitZeroBalances": "true",
 | ||
| 	}
 | ||
| 
 | ||
| 	balanceResp := binancedto.BinanceSpotAccount{}
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 	body, code, err := client.SendSpotAuth(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)
 | ||
| 
 | ||
| 	if len(balanceResp.Balances) > 0 {
 | ||
| 		for _, item := range balanceResp.Balances {
 | ||
| 			if strings.ToUpper(item.Asset) == "USDT" {
 | ||
| 				free := utility.StrToDecimal(item.Free)
 | ||
| 				lock := utility.StrToDecimal(item.Locked)
 | ||
| 				data.SpotFreeAmount = utility.StrToDecimal(item.Free)
 | ||
| 				data.SpotTotalAmount = free.Add(lock)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // 万象划转
 | ||
| func TradeAmount(db *gorm.DB, req *binancedto.BinanceTransfer, apiUserInfo DbModels.LineApiUser) error {
 | ||
| 	url := "/sapi/v1/asset/transfer"
 | ||
| 
 | ||
| 	client := GetClient(&apiUserInfo)
 | ||
| 
 | ||
| 	params := map[string]string{
 | ||
| 		"type":       req.Type,
 | ||
| 		"asset":      req.Asset,
 | ||
| 		"amount":     req.Amount.String(),
 | ||
| 		"fromSymbol": req.FromSymbol,
 | ||
| 		"toSymbol":   req.ToSymbol,
 | ||
| 		"recvWindow": "10000",
 | ||
| 	}
 | ||
| 
 | ||
| 	_, code, err := client.SendSpotAuth(url, "POST", 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())
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 |