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 } // 获取合约 持仓价格、数量 // symbol:交易对 // positionSide:持仓方向 // holdeData:持仓数据 func (e FutRestApi) GetPositionData(apiInfo *DbModels.LineApiUser, symbol, positionSide string, holdeData *HoldeData) error { opts := retryhelper.DefaultRetryOptions() opts.RetryableErrFn = func(err error) bool { if strings.Contains(err.Error(), "LOT_SIZE") { return false } //重试 return true } holdes, err := retryhelper.RetryWithResult(func() ([]PositionRisk, error) { return e.GetPositionV3(apiInfo, symbol) }, opts) if err != nil { return err } for _, item := range holdes { positionAmount, _ := decimal.NewFromString(item.PositionAmt) if (positionSide == "LONG" && item.PositionSide == "BOTH" && positionAmount.Cmp(decimal.Zero) > 0) || item.PositionSide == positionSide { //多 holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice) holdeData.TotalQuantity = positionAmount.Abs() continue } else if (positionSide == "SHORT" && item.PositionSide == "BOTH" && positionAmount.Cmp(decimal.Zero) < 0) || item.PositionSide == positionSide { //空 holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice) holdeData.TotalQuantity = positionAmount.Abs() continue } } 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 } // 带重试的批量撤销订单 func (e FutRestApi) CancelBatchFutOrderLoop(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderIdList []string) error { opts := retryhelper.DefaultRetryOptions() opts.RetryableErrFn = func(err error) bool { if strings.Contains(err.Error(), "LOT_SIZE") { return false } //重试 return true } err := retryhelper.Retry(func() error { return e.CancelBatchFutOrder(apiUserInfo, symbol, newClientOrderIdList) }, opts) if err != nil { return err } 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 }