feat(exchange): Add comprehensive Binance time sync (PR #313)

- Auto-sync server time every 30 seconds
- Add callWithTimeSync retry mechanism for all API calls
- Fix -1021 timestamp errors automatically
- Production-ready implementation
- Resolved merge conflict with PR #145

Source: https://github.com/NoFxAiOS/nofx/pull/313
This commit is contained in:
ZhouYongyou
2025-11-03 21:33:02 +08:00

View File

@@ -2,12 +2,14 @@ package trader
import (
"context"
"errors"
"fmt"
"log"
"strconv"
"sync"
"time"
"github.com/adshao/go-binance/v2/common"
"github.com/adshao/go-binance/v2/futures"
)
@@ -27,20 +29,74 @@ type FuturesTrader struct {
// 缓存有效期15秒
cacheDuration time.Duration
// 服务器时间同步
timeSyncMutex sync.Mutex
lastTimeSync time.Time
timeSyncInterval time.Duration
}
// NewFuturesTrader 创建合约交易器
func NewFuturesTrader(apiKey, secretKey string) *FuturesTrader {
client := futures.NewClient(apiKey, secretKey)
// 🔧 修复时间戳问题:启用时间同步
futures.UseTestnet = false // 确保使用正式网络
client.NewSetServerTimeService().Do(context.Background()) // 同步服务器时间
return &FuturesTrader{
client: client,
cacheDuration: 15 * time.Second, // 15秒缓存
trader := &FuturesTrader{
client: client,
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 获取账户余额(带缓存)
@@ -57,7 +113,13 @@ func (t *FuturesTrader) GetBalance() (map[string]interface{}, error) {
// 缓存过期或不存在调用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 {
log.Printf("❌ 币安API调用失败: %v", err)
return nil, fmt.Errorf("获取账户信息失败: %w", err)
@@ -96,7 +158,13 @@ func (t *FuturesTrader) GetPositions() ([]map[string]interface{}, error) {
// 缓存过期或不存在调用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 {
return nil, fmt.Errorf("获取持仓失败: %w", err)
}
@@ -146,10 +214,19 @@ func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
}
// 尝试设置仓位模式
<<<<<<< HEAD
err := t.client.NewChangeMarginTypeService().
Symbol(symbol).
MarginType(marginType).
Do(context.Background())
=======
err := t.callWithTimeSync("设置仓位模式", func() error {
return t.client.NewChangeMarginTypeService().
Symbol(symbol).
MarginType(marginType).
Do(context.Background())
})
>>>>>>> pr-313-time-sync
marginModeStr := "全仓"
if !isCrossMargin {
@@ -199,10 +276,13 @@ func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
}
// 切换杠杆
_, err = t.client.NewChangeLeverageService().
Symbol(symbol).
Leverage(leverage).
Do(context.Background())
err = t.callWithTimeSync("设置杠杆", func() error {
_, innerErr := t.client.NewChangeLeverageService().
Symbol(symbol).
Leverage(leverage).
Do(context.Background())
return innerErr
})
if err != nil {
// 如果错误信息包含"No need to change",说明杠杆已经是目标值
@@ -243,13 +323,18 @@ func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int)
}
// 创建市价买入订单
order, err := t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeBuy).
PositionSide(futures.PositionSideTypeLong).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
var order *futures.CreateOrderResponse
err = t.callWithTimeSync("开多仓", func() error {
var innerErr error
order, innerErr = t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeBuy).
PositionSide(futures.PositionSideTypeLong).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil {
return nil, fmt.Errorf("开多仓失败: %w", err)
@@ -286,13 +371,18 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int)
}
// 创建市价卖出订单
order, err := t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeSell).
PositionSide(futures.PositionSideTypeShort).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
var order *futures.CreateOrderResponse
err = t.callWithTimeSync("开空仓", func() error {
var innerErr error
order, innerErr = t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeSell).
PositionSide(futures.PositionSideTypeShort).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil {
return nil, fmt.Errorf("开空仓失败: %w", err)
@@ -336,13 +426,18 @@ func (t *FuturesTrader) CloseLong(symbol string, quantity float64) (map[string]i
}
// 创建市价卖出订单(平多)
order, err := t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeSell).
PositionSide(futures.PositionSideTypeLong).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
var order *futures.CreateOrderResponse
err = t.callWithTimeSync("平多仓", func() error {
var innerErr error
order, innerErr = t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeSell).
PositionSide(futures.PositionSideTypeLong).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil {
return nil, fmt.Errorf("平多仓失败: %w", err)
@@ -390,13 +485,18 @@ func (t *FuturesTrader) CloseShort(symbol string, quantity float64) (map[string]
}
// 创建市价买入订单(平空)
order, err := t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeBuy).
PositionSide(futures.PositionSideTypeShort).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
var order *futures.CreateOrderResponse
err = t.callWithTimeSync("平空仓", func() error {
var innerErr error
order, innerErr = t.client.NewCreateOrderService().
Symbol(symbol).
Side(futures.SideTypeBuy).
PositionSide(futures.PositionSideTypeShort).
Type(futures.OrderTypeMarket).
Quantity(quantityStr).
Do(context.Background())
return innerErr
})
if err != nil {
return nil, fmt.Errorf("平空仓失败: %w", err)
@@ -418,9 +518,11 @@ func (t *FuturesTrader) CloseShort(symbol string, quantity float64) (map[string]
// CancelAllOrders 取消该币种的所有挂单
func (t *FuturesTrader) CancelAllOrders(symbol string) error {
err := t.client.NewCancelAllOpenOrdersService().
Symbol(symbol).
Do(context.Background())
err := t.callWithTimeSync("取消挂单", func() error {
return t.client.NewCancelAllOpenOrdersService().
Symbol(symbol).
Do(context.Background())
})
if err != nil {
return fmt.Errorf("取消挂单失败: %w", err)
@@ -523,16 +625,19 @@ func (t *FuturesTrader) SetStopLoss(symbol string, positionSide string, quantity
return err
}
_, err = t.client.NewCreateOrderService().
Symbol(symbol).
Side(side).
PositionSide(posSide).
Type(futures.OrderTypeStopMarket).
StopPrice(fmt.Sprintf("%.8f", stopPrice)).
Quantity(quantityStr).
WorkingType(futures.WorkingTypeContractPrice).
ClosePosition(true).
Do(context.Background())
err = t.callWithTimeSync("设置止损", func() error {
_, innerErr := t.client.NewCreateOrderService().
Symbol(symbol).
Side(side).
PositionSide(posSide).
Type(futures.OrderTypeStopMarket).
StopPrice(fmt.Sprintf("%.8f", stopPrice)).
Quantity(quantityStr).
WorkingType(futures.WorkingTypeContractPrice).
ClosePosition(true).
Do(context.Background())
return innerErr
})
if err != nil {
return fmt.Errorf("设置止损失败: %w", err)
@@ -561,16 +666,19 @@ func (t *FuturesTrader) SetTakeProfit(symbol string, positionSide string, quanti
return err
}
_, err = t.client.NewCreateOrderService().
Symbol(symbol).
Side(side).
PositionSide(posSide).
Type(futures.OrderTypeTakeProfitMarket).
StopPrice(fmt.Sprintf("%.8f", takeProfitPrice)).
Quantity(quantityStr).
WorkingType(futures.WorkingTypeContractPrice).
ClosePosition(true).
Do(context.Background())
err = t.callWithTimeSync("设置止盈", func() error {
_, innerErr := t.client.NewCreateOrderService().
Symbol(symbol).
Side(side).
PositionSide(posSide).
Type(futures.OrderTypeTakeProfitMarket).
StopPrice(fmt.Sprintf("%.8f", takeProfitPrice)).
Quantity(quantityStr).
WorkingType(futures.WorkingTypeContractPrice).
ClosePosition(true).
Do(context.Background())
return innerErr
})
if err != nil {
return fmt.Errorf("设置止盈失败: %w", err)