Files
exchange_go/services/binanceservice/binancerest.go
2025-05-19 09:47:49 +08:00

683 lines
20 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 (
"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
}
/*
根据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
}