2023-06-29 02:56:07 +00:00
|
|
|
package retry
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-02-22 06:25:44 +00:00
|
|
|
"errors"
|
2023-11-30 09:09:25 +00:00
|
|
|
"fmt"
|
2023-06-29 02:56:07 +00:00
|
|
|
"strconv"
|
|
|
|
|
2023-10-30 09:17:36 +00:00
|
|
|
"github.com/cenkalti/backoff/v4"
|
2023-06-29 02:56:07 +00:00
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
2024-02-22 06:25:44 +00:00
|
|
|
var ErrOrderIsNil = errors.New("order object is nil")
|
|
|
|
|
2023-06-29 02:56:07 +00:00
|
|
|
type advancedOrderCancelService interface {
|
|
|
|
CancelAllOrders(ctx context.Context) ([]types.Order, error)
|
|
|
|
CancelOrdersBySymbol(ctx context.Context, symbol string) ([]types.Order, error)
|
|
|
|
CancelOrdersByGroupID(ctx context.Context, groupID uint32) ([]types.Order, error)
|
|
|
|
}
|
|
|
|
|
2023-11-30 09:09:25 +00:00
|
|
|
func QueryOrderUntilCanceled(
|
|
|
|
ctx context.Context, queryOrderService types.ExchangeOrderQueryService, symbol string, orderId uint64,
|
|
|
|
) (o *types.Order, err error) {
|
|
|
|
var op = func() (err2 error) {
|
|
|
|
o, err2 = queryOrderService.QueryOrder(ctx, types.OrderQuery{
|
|
|
|
Symbol: symbol,
|
|
|
|
OrderID: strconv.FormatUint(orderId, 10),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err2 != nil {
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
if o == nil {
|
|
|
|
return fmt.Errorf("order #%d response is nil", orderId)
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.Status == types.OrderStatusCanceled || o.Status == types.OrderStatusFilled {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("order #%d is not canceled yet: %s", o.OrderID, o.Status)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = GeneralBackoff(ctx, op)
|
|
|
|
return o, err
|
|
|
|
}
|
|
|
|
|
2023-11-20 08:20:39 +00:00
|
|
|
func QueryOrderUntilFilled(
|
|
|
|
ctx context.Context, queryOrderService types.ExchangeOrderQueryService, symbol string, orderId uint64,
|
|
|
|
) (o *types.Order, err error) {
|
2023-10-30 09:17:36 +00:00
|
|
|
var op = func() (err2 error) {
|
2023-06-29 02:56:07 +00:00
|
|
|
o, err2 = queryOrderService.QueryOrder(ctx, types.OrderQuery{
|
|
|
|
Symbol: symbol,
|
|
|
|
OrderID: strconv.FormatUint(orderId, 10),
|
|
|
|
})
|
|
|
|
|
2024-02-21 08:54:25 +00:00
|
|
|
if err2 != nil {
|
2023-06-29 02:56:07 +00:00
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
2024-02-22 06:25:44 +00:00
|
|
|
if o == nil {
|
|
|
|
return ErrOrderIsNil
|
|
|
|
}
|
|
|
|
|
2024-02-21 08:54:25 +00:00
|
|
|
// for final status return nil error to stop the retry
|
|
|
|
switch o.Status {
|
|
|
|
case types.OrderStatusFilled, types.OrderStatusCanceled:
|
|
|
|
return nil
|
2023-06-29 02:56:07 +00:00
|
|
|
}
|
|
|
|
|
2024-02-21 08:54:25 +00:00
|
|
|
return fmt.Errorf("order is not filled yet: status=%s E/Q=%s/%s", o.Status, o.ExecutedQuantity.String(), o.Quantity.String())
|
2023-10-30 09:17:36 +00:00
|
|
|
}
|
2023-06-29 02:56:07 +00:00
|
|
|
|
2023-10-30 09:17:36 +00:00
|
|
|
err = GeneralBackoff(ctx, op)
|
2023-06-29 02:56:07 +00:00
|
|
|
return o, err
|
|
|
|
}
|
|
|
|
|
2023-10-30 09:17:36 +00:00
|
|
|
func GeneralBackoff(ctx context.Context, op backoff.Operation) (err error) {
|
|
|
|
err = backoff.Retry(op, backoff.WithContext(
|
|
|
|
backoff.WithMaxRetries(
|
|
|
|
backoff.NewExponentialBackOff(),
|
2023-06-29 02:56:07 +00:00
|
|
|
101),
|
|
|
|
ctx))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-30 09:17:36 +00:00
|
|
|
func GeneralLiteBackoff(ctx context.Context, op backoff.Operation) (err error) {
|
|
|
|
err = backoff.Retry(op, backoff.WithContext(
|
|
|
|
backoff.WithMaxRetries(
|
|
|
|
backoff.NewExponentialBackOff(),
|
2023-10-30 08:28:34 +00:00
|
|
|
5),
|
|
|
|
ctx))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-11-20 08:20:39 +00:00
|
|
|
func QueryOpenOrdersUntilSuccessful(
|
|
|
|
ctx context.Context, ex types.Exchange, symbol string,
|
|
|
|
) (openOrders []types.Order, err error) {
|
2023-06-29 02:56:07 +00:00
|
|
|
var op = func() (err2 error) {
|
|
|
|
openOrders, err2 = ex.QueryOpenOrders(ctx, symbol)
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
err = GeneralBackoff(ctx, op)
|
|
|
|
return openOrders, err
|
|
|
|
}
|
|
|
|
|
2023-11-20 08:20:39 +00:00
|
|
|
func QueryOpenOrdersUntilSuccessfulLite(
|
|
|
|
ctx context.Context, ex types.Exchange, symbol string,
|
|
|
|
) (openOrders []types.Order, err error) {
|
2023-10-30 08:28:34 +00:00
|
|
|
var op = func() (err2 error) {
|
|
|
|
openOrders, err2 = ex.QueryOpenOrders(ctx, symbol)
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
2023-10-30 09:17:36 +00:00
|
|
|
err = GeneralLiteBackoff(ctx, op)
|
2023-10-30 08:28:34 +00:00
|
|
|
return openOrders, err
|
|
|
|
}
|
|
|
|
|
2023-11-20 08:20:39 +00:00
|
|
|
func QueryAccountUntilSuccessful(
|
|
|
|
ctx context.Context, ex types.ExchangeAccountService,
|
|
|
|
) (account *types.Account, err error) {
|
|
|
|
var op = func() (err2 error) {
|
|
|
|
account, err2 = ex.QueryAccount(ctx)
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
err = GeneralBackoff(ctx, op)
|
|
|
|
return account, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func QueryOrderUntilSuccessful(
|
|
|
|
ctx context.Context, query types.ExchangeOrderQueryService, opts types.OrderQuery,
|
|
|
|
) (order *types.Order, err error) {
|
2023-09-19 03:12:14 +00:00
|
|
|
var op = func() (err2 error) {
|
|
|
|
order, err2 = query.QueryOrder(ctx, opts)
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
err = GeneralBackoff(ctx, op)
|
|
|
|
return order, err
|
|
|
|
}
|
|
|
|
|
2023-06-29 02:56:07 +00:00
|
|
|
func CancelAllOrdersUntilSuccessful(ctx context.Context, service advancedOrderCancelService) error {
|
|
|
|
var op = func() (err2 error) {
|
|
|
|
_, err2 = service.CancelAllOrders(ctx)
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
return GeneralBackoff(ctx, op)
|
|
|
|
}
|
|
|
|
|
|
|
|
func CancelOrdersUntilSuccessful(ctx context.Context, ex types.Exchange, orders ...types.Order) error {
|
|
|
|
var op = func() (err2 error) {
|
|
|
|
err2 = ex.CancelOrders(ctx, orders...)
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
return GeneralBackoff(ctx, op)
|
|
|
|
}
|