Files
exchange_go/services/binanceservice/futuresbinancerest.go
2025-10-14 19:58:59 +08:00

1017 lines
31 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/global"
"go-admin/common/helper"
"go-admin/models"
"go-admin/models/binancedto"
"go-admin/models/futuresdto"
"go-admin/pkg/httputils"
"go-admin/pkg/utility"
"go-admin/services/cacheservice"
"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(&paramsMaps)
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) {
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
}
// 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
}
// 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)
tickerSymbolMaps, _ := cacheservice.GetExchangeTradeSets(global.EXCHANGE_BINANCE, 1)
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.LastPrice)
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
}