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 }