feature: add cancelNoWait in GeneralOrderExecutor to make cancellation in 1s faster

This commit is contained in:
zenix 2022-10-17 13:38:58 +09:00 committed by Austin Liu
parent 763bb45842
commit 09c85d346c
3 changed files with 53 additions and 61 deletions

View File

@ -3,9 +3,9 @@ package bbgo
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/types"
@ -92,60 +92,49 @@ func (b *ActiveOrderBook) waitAllClear(ctx context.Context, waitTime, timeout ti
}
}
// Cancel cancels the given order from activeOrderBook gracefully
func (b *ActiveOrderBook) Cancel(ctx context.Context, ex types.Exchange, order types.Order) error {
if !b.Exists(order) {
return fmt.Errorf("cannot find %v in orderbook", order)
// Cancel orders without confirmation
func (b *ActiveOrderBook) CancelNoWait(ctx context.Context, ex types.Exchange, orders ...types.Order) error {
// if no orders are given, set to cancelAll
if len(orders) == 0 {
orders = b.Orders()
} else {
// simple check on given input
for _, o := range orders {
if o.Symbol != b.Symbol {
return errors.New("[ActiveOrderBook] cancel " + b.Symbol + " orderbook with different symbol: " + o.Symbol)
}
}
}
// optimize order cancel for back-testing
if IsBackTesting {
return ex.CancelOrders(context.Background(), order)
return ex.CancelOrders(context.Background(), orders...)
}
log.Debugf("[ActiveOrderBook] gracefully cancelling %v order...", order.OrderID)
waitTime := CancelOrderWaitTime
startTime := time.Now()
// ensure order is cancelled
for {
// Some orders in the variable are not created on the server side yet,
// If we cancel these orders directly, we will get an unsent order error
// We wait here for a while for server to create these orders.
// time.Sleep(SentOrderWaitTime)
// since ctx might be canceled, we should use background context here
if err := ex.CancelOrders(context.Background(), order); err != nil {
log.WithError(err).Errorf("[ActiveORderBook] can not cancel %v order", order.OrderID)
}
log.Debugf("[ActiveOrderBook] waiting %s for %v order to be cancelled...", waitTime, order.OrderID)
clear, err := b.waitClear(ctx, order, waitTime, 5*time.Second)
if clear || err != nil {
break
}
b.Print()
openOrders, err := ex.QueryOpenOrders(ctx, order.Symbol)
if err != nil {
log.WithError(err).Errorf("can not query %s open orders", order.Symbol)
continue
}
openOrderStore := NewOrderStore(order.Symbol)
openOrderStore.Add(openOrders...)
// if it's not on the order book (open orders), we should remove it from our local side
if !openOrderStore.Exists(order.OrderID) {
b.Remove(order)
}
log.Debugf("[ActiveOrderBook] no wait cancelling %s orders...", b.Symbol)
// since ctx might be canceled, we should use background context here
if err := ex.CancelOrders(context.Background(), orders...); err != nil {
log.WithError(err).Errorf("[ActiveOrderBook] no wait can not cancel %s orders", b.Symbol)
}
for _, o := range orders {
b.Remove(o)
}
log.Debugf("[ActiveOrderBook] %v(%s) order is cancelled successfully in %s", order.OrderID, b.Symbol, time.Since(startTime))
return nil
}
// GracefulCancel cancels the active orders gracefully
func (b *ActiveOrderBook) GracefulCancel(ctx context.Context, ex types.Exchange) error {
func (b *ActiveOrderBook) GracefulCancel(ctx context.Context, ex types.Exchange, orders ...types.Order) error {
// if no orders are given, set to cancelAll
if len(orders) == 0 {
orders = b.Orders()
} else {
// simple check on given input
for _, o := range orders {
if b.Symbol != "" && o.Symbol != b.Symbol {
return errors.New("[ActiveOrderBook] cancel " + b.Symbol + " orderbook with different symbol: " + o.Symbol)
}
}
}
// optimize order cancel for back-testing
if IsBackTesting {
orders := b.Orders()
return ex.CancelOrders(context.Background(), orders...)
}
@ -155,8 +144,6 @@ func (b *ActiveOrderBook) GracefulCancel(ctx context.Context, ex types.Exchange)
startTime := time.Now()
// ensure every order is cancelled
for {
orders := b.Orders()
// Some orders in the variable are not created on the server side yet,
// If we cancel these orders directly, we will get an unsent order error
// We wait here for a while for server to create these orders.
@ -180,12 +167,12 @@ func (b *ActiveOrderBook) GracefulCancel(ctx context.Context, ex types.Exchange)
// verify the current open orders via the RESTful API
log.Warnf("[ActiveOrderBook] using REStful API to verify active orders...")
orders = b.Orders()
var symbols = map[string]struct{}{}
for _, order := range orders {
symbols[order.Symbol] = struct{}{}
}
var leftOrders []types.Order
for symbol := range symbols {
openOrders, err := ex.QueryOpenOrders(ctx, symbol)
@ -200,9 +187,12 @@ func (b *ActiveOrderBook) GracefulCancel(ctx context.Context, ex types.Exchange)
// if it's not on the order book (open orders), we should remove it from our local side
if !openOrderStore.Exists(o.OrderID) {
b.Remove(o)
} else {
leftOrders = append(leftOrders, o)
}
}
}
orders = leftOrders
}
log.Debugf("[ActiveOrderBook] all %s orders are cancelled successfully in %s", b.Symbol, time.Since(startTime))

View File

@ -356,11 +356,11 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
}
// GracefulCancelActiveOrderBook cancels the orders from the active orderbook.
func (e *GeneralOrderExecutor) GracefulCancelActiveOrderBook(ctx context.Context, activeOrders *ActiveOrderBook) error {
func (e *GeneralOrderExecutor) GracefulCancelActiveOrderBook(ctx context.Context, activeOrders *ActiveOrderBook, orders ...types.Order) error {
if activeOrders.NumOfOrders() == 0 {
return nil
}
if err := activeOrders.GracefulCancel(ctx, e.session.Exchange); err != nil {
if err := activeOrders.GracefulCancel(ctx, e.session.Exchange, orders...); err != nil {
// Retry once
if err = activeOrders.GracefulCancel(ctx, e.session.Exchange); err != nil {
return fmt.Errorf("graceful cancel order error: %w", err)
@ -371,23 +371,25 @@ func (e *GeneralOrderExecutor) GracefulCancelActiveOrderBook(ctx context.Context
return nil
}
func (e *GeneralOrderExecutor) GracefulCancelOrder(ctx context.Context, order types.Order) error {
if e.activeMakerOrders.NumOfOrders() == 0 {
// CancelActiveOrderBookNoWait cancels the orders from the active orderbook without waiting
func (e *GeneralOrderExecutor) CancelActiveOrderBookNoWait(ctx context.Context, activeOrders *ActiveOrderBook, orders ...types.Order) error {
if activeOrders.NumOfOrders() == 0 {
return nil
}
if err := e.activeMakerOrders.Cancel(ctx, e.session.Exchange, order); err != nil {
// Retry once
if err = e.activeMakerOrders.Cancel(ctx, e.session.Exchange, order); err != nil {
return fmt.Errorf("cancel order error: %w", err)
}
if err := activeOrders.CancelNoWait(ctx, e.session.Exchange, orders...); err != nil {
return fmt.Errorf("cancel order error: %w", err)
}
e.tradeCollector.Process()
return nil
}
// GracefulCancel cancels all active maker orders
func (e *GeneralOrderExecutor) GracefulCancel(ctx context.Context) error {
return e.GracefulCancelActiveOrderBook(ctx, e.activeMakerOrders)
// GracefulCancel cancels all active maker orders if orders are not given, otherwise cancel all the given orders
func (e *GeneralOrderExecutor) GracefulCancel(ctx context.Context, orders ...types.Order) error {
return e.GracefulCancelActiveOrderBook(ctx, e.activeMakerOrders, orders...)
}
// CancelNoWait cancels all active maker orders if orders is not given, otherwise cancel the given orders
func (e *GeneralOrderExecutor) CancelNoWait(ctx context.Context, orders ...types.Order) error {
return e.CancelActiveOrderBookNoWait(ctx, e.activeMakerOrders, orders...)
}
// ClosePosition closes the current position by a percentage.

View File

@ -202,7 +202,7 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef float64) int {
panic("not supported side for the order")
}
if toCancel {
err := s.GeneralOrderExecutor.GracefulCancelOrder(ctx, order)
err := s.GeneralOrderExecutor.GracefulCancel(ctx, order)
if err == nil {
delete(s.orderPendingCounter, order.OrderID)
} else {