mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-16 01:06:59 +08:00
fix(binance): 同步服务器时间并在 -1021 时自动重试,稳定签名调用
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -141,10 +214,12 @@ func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 尝试设置仓位模式
|
// 尝试设置仓位模式
|
||||||
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 {
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user