fix(binance): 同步服务器时间并在 -1021 时自动重试,稳定签名调用

This commit is contained in:
xiehs211
2025-11-03 17:33:43 +08:00
parent 88325572e6
commit 9601d43c41

View File

@@ -2,12 +2,14 @@ package trader
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log" "log"
"strconv" "strconv"
"sync" "sync"
"time" "time"
"github.com/adshao/go-binance/v2/common"
"github.com/adshao/go-binance/v2/futures" "github.com/adshao/go-binance/v2/futures"
) )
@@ -27,15 +29,74 @@ type FuturesTrader struct {
// 缓存有效期15秒 // 缓存有效期15秒
cacheDuration time.Duration cacheDuration time.Duration
// 服务器时间同步
timeSyncMutex sync.Mutex
lastTimeSync time.Time
timeSyncInterval time.Duration
} }
// NewFuturesTrader 创建合约交易器 // NewFuturesTrader 创建合约交易器
func NewFuturesTrader(apiKey, secretKey string) *FuturesTrader { func NewFuturesTrader(apiKey, secretKey string) *FuturesTrader {
client := futures.NewClient(apiKey, secretKey) client := futures.NewClient(apiKey, secretKey)
return &FuturesTrader{ trader := &FuturesTrader{
client: client, client: client,
cacheDuration: 15 * time.Second, // 15秒缓存 cacheDuration: 15 * time.Second, // 15秒缓存
timeSyncInterval: 30 * time.Second,
} }
if err := trader.syncServerTime(context.Background(), true); err != nil {
log.Printf("⚠️ 初始化同步币安服务器时间失败: %v", err)
}
return trader
}
// syncServerTime 同步本地与币安服务器的时间偏移
func (t *FuturesTrader) syncServerTime(ctx context.Context, force bool) error {
t.timeSyncMutex.Lock()
defer t.timeSyncMutex.Unlock()
if !force && !t.lastTimeSync.IsZero() && time.Since(t.lastTimeSync) < t.timeSyncInterval {
return nil
}
offset, err := t.client.NewSetServerTimeService().Do(ctx)
if err != nil {
return err
}
t.lastTimeSync = time.Now()
drift := time.Duration(offset) * time.Millisecond
log.Printf("✓ Binance服务器时间同步成功 (offset=%s)", drift)
return nil
}
// callWithTimeSync 在调用需要签名的接口前后处理服务器时间同步,并在时间偏差错误时重试一次
func (t *FuturesTrader) callWithTimeSync(operation string, call func() error) error {
ctx := context.Background()
if err := t.syncServerTime(ctx, false); err != nil {
log.Printf("⚠️ 同步Binance服务器时间失败%s: %v", operation, err)
}
err := call()
if err == nil {
return nil
}
var apiErr *common.APIError
if errors.As(err, &apiErr) && apiErr.Code == -1021 {
log.Printf("⚠️ Binance返回时间偏差错误%s尝试强制同步后重试: %s", operation, apiErr.Message)
if syncErr := t.syncServerTime(ctx, true); syncErr != nil {
log.Printf("❌ Binance服务器时间强制同步失败: %v", syncErr)
return err
}
err = call()
}
return err
} }
// GetBalance 获取账户余额(带缓存) // GetBalance 获取账户余额(带缓存)
@@ -52,7 +113,13 @@ func (t *FuturesTrader) GetBalance() (map[string]interface{}, error) {
// 缓存过期或不存在调用API // 缓存过期或不存在调用API
log.Printf("🔄 缓存过期正在调用币安API获取账户余额...") log.Printf("🔄 缓存过期正在调用币安API获取账户余额...")
account, err := t.client.NewGetAccountService().Do(context.Background())
var account *futures.Account
err := t.callWithTimeSync("获取账户信息", func() error {
var innerErr error
account, innerErr = t.client.NewGetAccountService().Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
log.Printf("❌ 币安API调用失败: %v", err) log.Printf("❌ 币安API调用失败: %v", err)
return nil, fmt.Errorf("获取账户信息失败: %w", err) return nil, fmt.Errorf("获取账户信息失败: %w", err)
@@ -91,7 +158,13 @@ func (t *FuturesTrader) GetPositions() ([]map[string]interface{}, error) {
// 缓存过期或不存在调用API // 缓存过期或不存在调用API
log.Printf("🔄 缓存过期正在调用币安API获取持仓信息...") log.Printf("🔄 缓存过期正在调用币安API获取持仓信息...")
positions, err := t.client.NewGetPositionRiskService().Do(context.Background())
var positions []*futures.PositionRisk
err := t.callWithTimeSync("获取持仓信息", func() error {
var innerErr error
positions, innerErr = t.client.NewGetPositionRiskService().Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
return nil, fmt.Errorf("获取持仓失败: %w", err) return nil, fmt.Errorf("获取持仓失败: %w", err)
} }
@@ -139,18 +212,20 @@ func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
} else { } else {
marginType = futures.MarginTypeIsolated marginType = futures.MarginTypeIsolated
} }
// 尝试设置仓位模式 // 尝试设置仓位模式
err := t.client.NewChangeMarginTypeService(). err := t.callWithTimeSync("设置仓位模式", func() error {
Symbol(symbol). return t.client.NewChangeMarginTypeService().
MarginType(marginType). Symbol(symbol).
Do(context.Background()) MarginType(marginType).
Do(context.Background())
})
marginModeStr := "全仓" marginModeStr := "全仓"
if !isCrossMargin { if !isCrossMargin {
marginModeStr = "逐仓" marginModeStr = "逐仓"
} }
if err != nil { if err != nil {
// 如果错误信息包含"No need to change",说明仓位模式已经是目标值 // 如果错误信息包含"No need to change",说明仓位模式已经是目标值
if contains(err.Error(), "No need to change margin type") { if contains(err.Error(), "No need to change margin type") {
@@ -166,7 +241,7 @@ func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
// 不返回错误,让交易继续 // 不返回错误,让交易继续
return nil return nil
} }
log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginModeStr) log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginModeStr)
return nil return nil
} }
@@ -194,10 +269,13 @@ func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
} }
// 切换杠杆 // 切换杠杆
_, err = t.client.NewChangeLeverageService(). err = t.callWithTimeSync("设置杠杆", func() error {
Symbol(symbol). _, innerErr := t.client.NewChangeLeverageService().
Leverage(leverage). Symbol(symbol).
Do(context.Background()) Leverage(leverage).
Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
// 如果错误信息包含"No need to change",说明杠杆已经是目标值 // 如果错误信息包含"No need to change",说明杠杆已经是目标值
@@ -238,13 +316,18 @@ func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int)
} }
// 创建市价买入订单 // 创建市价买入订单
order, err := t.client.NewCreateOrderService(). var order *futures.CreateOrderResponse
Symbol(symbol). err = t.callWithTimeSync("开多仓", func() error {
Side(futures.SideTypeBuy). var innerErr error
PositionSide(futures.PositionSideTypeLong). order, innerErr = t.client.NewCreateOrderService().
Type(futures.OrderTypeMarket). Symbol(symbol).
Quantity(quantityStr). Side(futures.SideTypeBuy).
Do(context.Background()) PositionSide(futures.PositionSideTypeLong).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
return nil, fmt.Errorf("开多仓失败: %w", err) return nil, fmt.Errorf("开多仓失败: %w", err)
@@ -281,13 +364,18 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int)
} }
// 创建市价卖出订单 // 创建市价卖出订单
order, err := t.client.NewCreateOrderService(). var order *futures.CreateOrderResponse
Symbol(symbol). err = t.callWithTimeSync("开空仓", func() error {
Side(futures.SideTypeSell). var innerErr error
PositionSide(futures.PositionSideTypeShort). order, innerErr = t.client.NewCreateOrderService().
Type(futures.OrderTypeMarket). Symbol(symbol).
Quantity(quantityStr). Side(futures.SideTypeSell).
Do(context.Background()) PositionSide(futures.PositionSideTypeShort).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
return nil, fmt.Errorf("开空仓失败: %w", err) return nil, fmt.Errorf("开空仓失败: %w", err)
@@ -331,13 +419,18 @@ func (t *FuturesTrader) CloseLong(symbol string, quantity float64) (map[string]i
} }
// 创建市价卖出订单(平多) // 创建市价卖出订单(平多)
order, err := t.client.NewCreateOrderService(). var order *futures.CreateOrderResponse
Symbol(symbol). err = t.callWithTimeSync("平多仓", func() error {
Side(futures.SideTypeSell). var innerErr error
PositionSide(futures.PositionSideTypeLong). order, innerErr = t.client.NewCreateOrderService().
Type(futures.OrderTypeMarket). Symbol(symbol).
Quantity(quantityStr). Side(futures.SideTypeSell).
Do(context.Background()) PositionSide(futures.PositionSideTypeLong).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
return nil, fmt.Errorf("平多仓失败: %w", err) return nil, fmt.Errorf("平多仓失败: %w", err)
@@ -385,13 +478,18 @@ func (t *FuturesTrader) CloseShort(symbol string, quantity float64) (map[string]
} }
// 创建市价买入订单(平空) // 创建市价买入订单(平空)
order, err := t.client.NewCreateOrderService(). var order *futures.CreateOrderResponse
Symbol(symbol). err = t.callWithTimeSync("平空仓", func() error {
Side(futures.SideTypeBuy). var innerErr error
PositionSide(futures.PositionSideTypeShort). order, innerErr = t.client.NewCreateOrderService().
Type(futures.OrderTypeMarket). Symbol(symbol).
Quantity(quantityStr). Side(futures.SideTypeBuy).
Do(context.Background()) PositionSide(futures.PositionSideTypeShort).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
return nil, fmt.Errorf("平空仓失败: %w", err) return nil, fmt.Errorf("平空仓失败: %w", err)
@@ -413,9 +511,11 @@ func (t *FuturesTrader) CloseShort(symbol string, quantity float64) (map[string]
// CancelAllOrders 取消该币种的所有挂单 // CancelAllOrders 取消该币种的所有挂单
func (t *FuturesTrader) CancelAllOrders(symbol string) error { func (t *FuturesTrader) CancelAllOrders(symbol string) error {
err := t.client.NewCancelAllOpenOrdersService(). err := t.callWithTimeSync("取消挂单", func() error {
Symbol(symbol). return t.client.NewCancelAllOpenOrdersService().
Do(context.Background()) Symbol(symbol).
Do(context.Background())
})
if err != nil { if err != nil {
return fmt.Errorf("取消挂单失败: %w", err) return fmt.Errorf("取消挂单失败: %w", err)
@@ -471,16 +571,19 @@ func (t *FuturesTrader) SetStopLoss(symbol string, positionSide string, quantity
return err return err
} }
_, err = t.client.NewCreateOrderService(). err = t.callWithTimeSync("设置止损", func() error {
Symbol(symbol). _, innerErr := t.client.NewCreateOrderService().
Side(side). Symbol(symbol).
PositionSide(posSide). Side(side).
Type(futures.OrderTypeStopMarket). PositionSide(posSide).
StopPrice(fmt.Sprintf("%.8f", stopPrice)). Type(futures.OrderTypeStopMarket).
Quantity(quantityStr). StopPrice(fmt.Sprintf("%.8f", stopPrice)).
WorkingType(futures.WorkingTypeContractPrice). Quantity(quantityStr).
ClosePosition(true). WorkingType(futures.WorkingTypeContractPrice).
Do(context.Background()) ClosePosition(true).
Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
return fmt.Errorf("设置止损失败: %w", err) return fmt.Errorf("设置止损失败: %w", err)
@@ -509,16 +612,19 @@ func (t *FuturesTrader) SetTakeProfit(symbol string, positionSide string, quanti
return err return err
} }
_, err = t.client.NewCreateOrderService(). err = t.callWithTimeSync("设置止盈", func() error {
Symbol(symbol). _, innerErr := t.client.NewCreateOrderService().
Side(side). Symbol(symbol).
PositionSide(posSide). Side(side).
Type(futures.OrderTypeTakeProfitMarket). PositionSide(posSide).
StopPrice(fmt.Sprintf("%.8f", takeProfitPrice)). Type(futures.OrderTypeTakeProfitMarket).
Quantity(quantityStr). StopPrice(fmt.Sprintf("%.8f", takeProfitPrice)).
WorkingType(futures.WorkingTypeContractPrice). Quantity(quantityStr).
ClosePosition(true). WorkingType(futures.WorkingTypeContractPrice).
Do(context.Background()) ClosePosition(true).
Do(context.Background())
return innerErr
})
if err != nil { if err != nil {
return fmt.Errorf("设置止盈失败: %w", err) return fmt.Errorf("设置止盈失败: %w", err)