Files
exchange_go/services/binanceservice/futuresbinancerest.go

1003 lines
31 KiB
Go
Raw Normal View History

2025-02-06 11:14:33 +08:00
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"
2025-02-06 11:14:33 +08:00
"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
2025-02-08 14:05:57 +08:00
key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, item.Symbol)
2025-02-06 11:14:33 +08:00
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)
}
2025-02-08 14:05:57 +08:00
tcKey := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, item.Symbol)
2025-02-06 11:14:33 +08:00
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
}
2025-02-14 09:43:49 +08:00
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
}
2025-02-06 11:14:33 +08:00
// 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"
}
2025-02-11 18:03:30 +08:00
if strings.ToUpper(params.OrderType) == "TAKE_PROFIT" {
paramsMaps["timeInForce"] = "GTC"
paramsMaps["price"] = params.Price.String()
paramsMaps["stopprice"] = params.Profit.String()
2025-02-14 09:43:49 +08:00
// paramsMaps["workingType"] = "MARK_PRICE"
2025-02-11 18:03:30 +08:00
}
2025-02-06 11:14:33 +08:00
if strings.ToUpper(params.OrderType) == "STOP_MARKET" {
paramsMaps["stopprice"] = params.StopPrice.String()
paramsMaps["workingType"] = "MARK_PRICE"
paramsMaps["timeInForce"] = "GTC"
}
2025-02-10 17:55:34 +08:00
if strings.ToUpper(params.OrderType) == "STOP" {
paramsMaps["price"] = params.Price.String()
paramsMaps["stopprice"] = params.StopPrice.String()
paramsMaps["workingType"] = "MARK_PRICE"
paramsMaps["timeInForce"] = "GTC"
}
2025-02-06 11:14:33 +08:00
}
2025-02-10 17:55:34 +08:00
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" {
2025-02-06 11:14:33 +08:00
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(&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) {
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
}
// 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 {
2025-02-11 14:49:16 +08:00
return fmt.Errorf("api_id:%d 交易对:%s 撤销合约委托出错:%s", apiUserInfo.Id, symbol, err.Error())
2025-02-06 11:14:33 +08:00
}
code, ok := dataMap["code"]
if ok {
errContent := FutErrorMaps[code.(float64)]
if errContent == "" {
errContent = err.Error()
}
2025-02-11 14:49:16 +08:00
return fmt.Errorf("api_id:%d 交易对:%s 撤销合约委托出错:%s", apiUserInfo.Id, symbol, errContent)
2025-02-06 11:14:33 +08:00
}
}
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 {
2025-02-11 14:49:16 +08:00
return fmt.Errorf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, "最多支持10个订单")
2025-02-06 11:14:33 +08:00
}
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 {
2025-02-11 14:49:16 +08:00
return fmt.Errorf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, err.Error())
2025-02-06 11:14:33 +08:00
}
}
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) {
2025-02-14 09:43:49 +08:00
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))
2025-02-06 11:14:33 +08:00
}
}
2025-02-14 09:43:49 +08:00
// 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)
// }
// }
2025-02-06 11:14:33 +08:00
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
}