diff --git a/pkg/exchange/bitget/bitgetapi/v2/get_unfilled_orders_request.go b/pkg/exchange/bitget/bitgetapi/v2/get_unfilled_orders_request.go index d597df1d0..3bf57ddc6 100644 --- a/pkg/exchange/bitget/bitgetapi/v2/get_unfilled_orders_request.go +++ b/pkg/exchange/bitget/bitgetapi/v2/get_unfilled_orders_request.go @@ -38,13 +38,17 @@ type GetUnfilledOrdersRequest struct { client requestgen.AuthenticatedAPIClient symbol *string `param:"symbol,query"` - // Limit number default 100 max 100 + + // limit number default 100 max 100 limit *string `param:"limit,query"` + // idLessThan requests the content on the page before this ID (older data), the value input should be the orderId of the corresponding interface. - idLessThan *string `param:"idLessThan,query"` - startTime *time.Time `param:"startTime,milliseconds,query"` - endTime *time.Time `param:"endTime,milliseconds,query"` - orderId *string `param:"orderId,query"` + idLessThan *string `param:"idLessThan,query"` + + startTime *time.Time `param:"startTime,milliseconds,query"` + endTime *time.Time `param:"endTime,milliseconds,query"` + + orderId *string `param:"orderId,query"` } func (c *Client) NewGetUnfilledOrdersRequest() *GetUnfilledOrdersRequest { diff --git a/pkg/exchange/bitget/convert.go b/pkg/exchange/bitget/convert.go index 94c1b0678..ff5fb6258 100644 --- a/pkg/exchange/bitget/convert.go +++ b/pkg/exchange/bitget/convert.go @@ -191,7 +191,7 @@ func unfilledOrderToGlobalOrder(order v2.UnfilledOrder) (*types.Order, error) { // The market order will be executed immediately, so this check is used to handle corner cases. if orderType == types.OrderTypeMarket { qty = order.BaseVolume - log.Warnf("!!! The price(%f) and quantity(%f) are not verified for market orders, because we only receive limit orders in the test environment !!!", price.Float64(), qty.Float64()) + log.Warnf("!!! The price(%f) and quantity(%f) are not verified for market orders, because we only receive limit orders in the test environment !!!", price.Float64(), qty.Float64()) } return &types.Order{ @@ -202,6 +202,7 @@ func unfilledOrderToGlobalOrder(order v2.UnfilledOrder) (*types.Order, error) { Type: orderType, Quantity: qty, Price: price, + // Bitget does not include the "time-in-force" field in its API response for spot trading, so we set GTC. TimeInForce: types.TimeInForceGTC, }, diff --git a/pkg/exchange/bitget/exchange.go b/pkg/exchange/bitget/exchange.go index 62ab09091..de873205e 100644 --- a/pkg/exchange/bitget/exchange.go +++ b/pkg/exchange/bitget/exchange.go @@ -14,6 +14,7 @@ import ( "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi" v2 "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi/v2" "github.com/c9s/bbgo/pkg/types" + "github.com/c9s/bbgo/pkg/util" ) const ( @@ -34,26 +35,45 @@ var log = logrus.WithFields(logrus.Fields{ var ( // queryMarketRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-symbols queryMarketRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5) + // queryAccountRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-account-assets queryAccountRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5) + // queryTickerRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-single-ticker queryTickerRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5) + // queryTickersRateLimiter has its own rate limit. https://bitgetlimited.github.io/apidoc/en/spot/#get-all-tickers queryTickersRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5) + // queryOpenOrdersRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Get-Unfilled-Orders queryOpenOrdersRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5) + // closedQueryOrdersRateLimiter has its own rate limit. https://www.bitget.com/api-doc/spot/trade/Get-History-Orders closedQueryOrdersRateLimiter = rate.NewLimiter(rate.Every(time.Second/15), 5) - // submitOrdersRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Place-Order - submitOrdersRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5) + + // submitOrderRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Place-Order + submitOrderRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5) + // queryTradeRateLimiter has its own rate limit. https://www.bitget.com/zh-CN/api-doc/spot/trade/Get-Fills queryTradeRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5) + // cancelOrderRateLimiter has its own rate limit. https://www.bitget.com/api-doc/spot/trade/Cancel-Order cancelOrderRateLimiter = rate.NewLimiter(rate.Every(time.Second/5), 5) + // kLineRateLimiter has its own rate limit. https://www.bitget.com/api-doc/spot/market/Get-Candle-Data - kLineOrderRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5) + kLineRateLimiter = rate.NewLimiter(rate.Every(time.Second/10), 5) ) +var debugf func(msg string, args ...interface{}) + +func init() { + if v, ok := util.GetEnvVarBool("DEBUG_BITGET"); ok && v { + debugf = log.Infof + } else { + debugf = func(msg string, args ...interface{}) {} + } +} + type Exchange struct { key, secret, passphrase string @@ -199,7 +219,7 @@ func (e *Exchange) QueryKLines( req.EndTime(*options.EndTime) } - if err := kLineOrderRateLimiter.Wait(ctx); err != nil { + if err := kLineRateLimiter.Wait(ctx); err != nil { return nil, fmt.Errorf("query klines rate limiter wait error: %w", err) } @@ -322,26 +342,34 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr req.ClientOrderId(order.ClientOrderID) } - if err := submitOrdersRateLimiter.Wait(ctx); err != nil { + if err := submitOrderRateLimiter.Wait(ctx); err != nil { return nil, fmt.Errorf("place order rate limiter wait error: %w", err) } + res, err := req.Do(ctx) if err != nil { return nil, fmt.Errorf("failed to place order, order: %#v, err: %w", order, err) } + debugf("order created: %+v", res) + if len(res.OrderId) == 0 || (len(order.ClientOrderID) != 0 && res.ClientOrderId != order.ClientOrderID) { return nil, fmt.Errorf("unexpected order id, resp: %#v, order: %#v", res, order) } orderId := res.OrderId + + debugf("fetching unfilled order info for order #%s", orderId) ordersResp, err := e.v2client.NewGetUnfilledOrdersRequest().OrderId(orderId).Do(ctx) if err != nil { return nil, fmt.Errorf("failed to query open order by order id: %s, err: %w", orderId, err) } - switch len(ordersResp) { - case 0: + debugf("unfilled order response: %+v", ordersResp) + + if len(ordersResp) == 1 { + return unfilledOrderToGlobalOrder(ordersResp[0]) + } else if len(ordersResp) == 0 { // The market order will be executed immediately, so we cannot retrieve it through the NewGetUnfilledOrdersRequest API. // Try to get the order from the NewGetHistoryOrdersRequest API. ordersResp, err := e.v2client.NewGetHistoryOrdersRequest().OrderId(orderId).Do(ctx) @@ -354,13 +382,9 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr } return toGlobalOrder(ordersResp[0]) - - case 1: - return unfilledOrderToGlobalOrder(ordersResp[0]) - - default: - return nil, fmt.Errorf("unexpected order length, order id: %s", orderId) } + + return nil, fmt.Errorf("unexpected order length, order id: %s", orderId) } func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) { diff --git a/pkg/strategy/xdepthmaker/strategy.go b/pkg/strategy/xdepthmaker/strategy.go index 7173cec99..5bdf04cb0 100644 --- a/pkg/strategy/xdepthmaker/strategy.go +++ b/pkg/strategy/xdepthmaker/strategy.go @@ -647,14 +647,10 @@ func (s *Strategy) generateMakerOrders( // copy the pricing book because during the generation the book data could change dupPricingBook := pricingBook.Copy() - log.Infof("dupPricingBook: \n\tbids: %+v \n\tasks: %+v", + log.Infof("pricingBook: \n\tbids: %+v \n\tasks: %+v", dupPricingBook.SideBook(types.SideTypeBuy), dupPricingBook.SideBook(types.SideTypeSell)) - log.Infof("pricingBook: \n\tbids: %+v \n\tasks: %+v", - pricingBook.SideBook(types.SideTypeBuy), - pricingBook.SideBook(types.SideTypeSell)) - if maxLayer == 0 || maxLayer > s.NumLayers { maxLayer = s.NumLayers }