120 lines
3.0 KiB
Go
120 lines
3.0 KiB
Go
|
package tradingutil
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
|
||
|
log "github.com/sirupsen/logrus"
|
||
|
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/exchange/retry"
|
||
|
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
|
||
|
)
|
||
|
|
||
|
type CancelAllOrdersService interface {
|
||
|
CancelAllOrders(ctx context.Context) ([]types.Order, error)
|
||
|
}
|
||
|
|
||
|
type CancelAllOrdersBySymbolService interface {
|
||
|
CancelOrdersBySymbol(ctx context.Context, symbol string) ([]types.Order, error)
|
||
|
}
|
||
|
|
||
|
type CancelAllOrdersByGroupIDService interface {
|
||
|
CancelOrdersByGroupID(ctx context.Context, groupID uint32) ([]types.Order, error)
|
||
|
}
|
||
|
|
||
|
// UniversalCancelAllOrders checks if the exchange instance supports the best order cancel strategy
|
||
|
// it tries the first interface CancelAllOrdersService that does not need any existing order information or symbol information.
|
||
|
//
|
||
|
// if CancelAllOrdersService is not supported, then it tries CancelAllOrdersBySymbolService which needs at least one symbol
|
||
|
// for the cancel api request.
|
||
|
func UniversalCancelAllOrders(ctx context.Context, exchange types.Exchange, openOrders []types.Order) error {
|
||
|
if service, ok := exchange.(CancelAllOrdersService); ok {
|
||
|
if _, err := service.CancelAllOrders(ctx); err == nil {
|
||
|
return nil
|
||
|
} else {
|
||
|
log.WithError(err).Errorf("unable to cancel all orders")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(openOrders) == 0 {
|
||
|
return errors.New("to cancel all orders, openOrders can not be empty")
|
||
|
}
|
||
|
|
||
|
var anyErr error
|
||
|
if service, ok := exchange.(CancelAllOrdersBySymbolService); ok {
|
||
|
var symbols = CollectOrderSymbols(openOrders)
|
||
|
for _, symbol := range symbols {
|
||
|
_, err := service.CancelOrdersBySymbol(ctx, symbol)
|
||
|
if err != nil {
|
||
|
anyErr = err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if anyErr == nil {
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if service, ok := exchange.(CancelAllOrdersByGroupIDService); ok {
|
||
|
var groupIds = CollectOrderGroupIds(openOrders)
|
||
|
for _, groupId := range groupIds {
|
||
|
if _, err := service.CancelOrdersByGroupID(ctx, groupId); err != nil {
|
||
|
anyErr = err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if anyErr == nil {
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if anyErr != nil {
|
||
|
return anyErr
|
||
|
}
|
||
|
|
||
|
return fmt.Errorf("unable to cancel all orders, openOrders:%+v", openOrders)
|
||
|
}
|
||
|
|
||
|
func CollectOrderGroupIds(orders []types.Order) (groupIds []uint32) {
|
||
|
groupIdMap := map[uint32]struct{}{}
|
||
|
for _, o := range orders {
|
||
|
if o.GroupID > 0 {
|
||
|
groupIdMap[o.GroupID] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for id := range groupIdMap {
|
||
|
groupIds = append(groupIds, id)
|
||
|
}
|
||
|
|
||
|
return groupIds
|
||
|
}
|
||
|
|
||
|
func CollectOrderSymbols(orders []types.Order) (symbols []string) {
|
||
|
symbolMap := map[string]struct{}{}
|
||
|
for _, o := range orders {
|
||
|
symbolMap[o.Symbol] = struct{}{}
|
||
|
}
|
||
|
|
||
|
for s := range symbolMap {
|
||
|
symbols = append(symbols, s)
|
||
|
}
|
||
|
|
||
|
return symbols
|
||
|
}
|
||
|
|
||
|
func CollectOpenOrders(ctx context.Context, ex types.Exchange, symbols ...string) ([]types.Order, error) {
|
||
|
var collectedOrders []types.Order
|
||
|
for _, symbol := range symbols {
|
||
|
openOrders, err := retry.QueryOpenOrdersUntilSuccessful(ctx, ex, symbol)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
collectedOrders = append(collectedOrders, openOrders...)
|
||
|
}
|
||
|
|
||
|
return collectedOrders, nil
|
||
|
}
|