package binanceservice import ( "context" "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/futuresdto" "go-admin/pkg/httputils" "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) 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 } // 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 } var paramsMaps map[string]string 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) == "STOP_MARKET" { 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" { 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" } } var apiUserInfo DbModels.LineApiUser err := orm.Model(&DbModels.LineApiUser{}).Where("id = ?", params.ApiId).Find(&apiUserInfo).Error if err != nil { return err } 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) } _, 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 } // ClosePositionB 平仓B对应的交易对 // bApiUserInfo B 账户api-user信息 // symbol 需要平仓的交易对 // closeType 平仓模式 ALL = 全平 reduceOnly = 只减仓 // func (e FutRestApi) ClosePositionB(orm *gorm.DB, bApiUserInfo *DbModels.LineApiUser, symbol string, closeType string) error { // if bApiUserInfo == nil { // return errors.New("缺失平仓账户信息") // } // if symbol == "" { // return errors.New("缺失平仓交易对信息") // } // risks, err := e.GetPositionV3(bApiUserInfo, symbol) // if err != nil { // return err // } // for _, risk := range risks { // if risk.Symbol == strings.ToUpper(symbol) { // //持仓数量 // positionAmt, _ := decimal.NewFromString(risk.PositionAmt) // side := "BUY" // if positionAmt.GreaterThan(decimal.Zero) { // side = "SELL" // } // var closeAmt decimal.Decimal // if strings.ToUpper(closeType) == "ALL" { //全部平仓 // closeAmt = positionAmt // } else { // //如果是只减仓的话 数量=仓位数量*比例 // closeAmt = positionAmt.Mul(decimal.NewFromFloat(reduceOnlyRate)) // } // err = e.ClosePosition(symbol, closeAmt, side, *bApiUserInfo, "MARKET", "", decimal.Zero) // if err != nil { // return err // } // orm.Model(&DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ?", bApiUserInfo.Id, symbol).Updates(map[string]interface{}{ // "status": "6", // }) // } // } // 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) { 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) } 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" } 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) } 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 JudgeFuturesPrice(tradeSet models.TradeSet) { preOrderVal, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.PreFutOrderList, global.EXCHANGE_BINANCE)) db := GetDBConnection() if len(preOrderVal) == 0 { return } futApi := FutRestApi{} for _, item := range preOrderVal { preOrder := dto.PreOrderRedisList{} if err := sonic.Unmarshal([]byte(item), &preOrder); err != nil { log.Error("反序列化失败") continue } if preOrder.Symbol != tradeSet.Coin+tradeSet.Currency { continue } orderPrice, _ := decimal.NewFromString(preOrder.Price) tradePrice, _ := decimal.NewFromString(tradeSet.LastPrice) if orderPrice.Cmp(decimal.Zero) == 0 || tradePrice.Cmp(decimal.Zero) == 0 { continue } //多 if (strings.ToUpper(preOrder.Site) == "BUY" && orderPrice.Cmp(tradePrice) >= 0) || (strings.ToUpper(preOrder.Site) == "SELL" && orderPrice.Cmp(tradePrice) <= 0) { futTriggerOrder(db, &preOrder, item, futApi) } } } // 分布式锁下单 func futTriggerOrder(db *gorm.DB, v *dto.PreOrderRedisList, item string, futApi FutRestApi) { lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, v.ApiId, v.Symbol), 200, 5, 100*time.Millisecond) if ok, err := lock.AcquireWait(context.Background()); err != nil { log.Debug("获取锁失败", err) return } else if ok { defer lock.Release() key := fmt.Sprintf(rediskey.PreFutOrderList, global.EXCHANGE_BINANCE) preOrder := DbModels.LinePreOrder{} if err := db.Where("id = ?", v.Id).First(&preOrder).Error; err != nil { log.Error("获取预下单失败", err) if errors.Is(err, gorm.ErrRecordNotFound) { log.Error("不存在待触发主单", item) helper.DefaultRedis.LRem(key, item) } return } hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item) if !hasrecord { log.Debug("预下单缓存中不存在", item) return } price, _ := decimal.NewFromString(v.Price) num, _ := decimal.NewFromString(preOrder.Num) if price.Cmp(decimal.Zero) == 0 { log.Error("价格不能为0") return } params := FutOrderPlace{ ApiId: v.ApiId, Symbol: v.Symbol, Side: v.Site, OrderType: preOrder.MainOrderType, SideType: preOrder.MainOrderType, Price: price, Quantity: num, NewClientOrderId: v.OrderSn, } preOrderVal, _ := sonic.MarshalString(&v) if err := futApi.OrderPlace(db, params); err != nil { log.Error("下单失败", v.Symbol, " err:", err) err := db.Model(&DbModels.LinePreOrder{}).Where("id =? and status='0'", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error if err != nil { log.Error("更新预下单状态失败") } if preOrderVal != "" { if _, err := helper.DefaultRedis.LRem(key, preOrderVal); err != nil { log.Error("删除redis 预下单失败:", err) } } return } if preOrderVal != "" { if _, err := helper.DefaultRedis.LRem(key, preOrderVal); err != nil { log.Error("删除redis 预下单失败:", err) } } if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Update("status", "1").Error; err != nil { log.Error("更新预下单状态失败 ordersn:", v.OrderSn, " status:1") } return } else { log.Error("获取锁失败") return } } // 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, //用户自定义订单号 } 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) } _, _, 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 errors.New(fmt.Sprintf("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 errors.New(fmt.Sprintf("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", } 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) } _, _, 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 errors.New(fmt.Sprintf("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), } 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) } _, 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 errors.New(fmt.Sprintf("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) var targetSymbol string 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 } // 加仓主账号 func (e FutRestApi) CoverAccountA(apiUserInfo DbModels.LineApiUser, symbol string) { } // GetFutSymbolLastPrice 获取现货交易对最新价格 func (e FutRestApi) GetFutSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) { 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 }