2020-11-02 14:14:01 +00:00
|
|
|
package bbgo
|
|
|
|
|
|
|
|
import (
|
2022-01-06 16:10:40 +00:00
|
|
|
"context"
|
2021-03-17 17:15:06 +00:00
|
|
|
"encoding/json"
|
2022-12-23 10:19:00 +00:00
|
|
|
"sort"
|
2023-08-17 09:16:27 +00:00
|
|
|
"sync"
|
2022-01-06 16:10:40 +00:00
|
|
|
"time"
|
2021-03-17 17:15:06 +00:00
|
|
|
|
2022-10-17 04:38:58 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-11-10 06:19:33 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2020-11-02 14:14:01 +00:00
|
|
|
|
2022-11-02 04:42:09 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/sigchan"
|
2020-11-02 14:14:01 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
2022-06-09 18:51:20 +00:00
|
|
|
const CancelOrderWaitTime = 20 * time.Millisecond
|
2022-01-14 16:17:52 +00:00
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
// ActiveOrderBook manages the local active order books.
|
2023-07-04 13:42:24 +00:00
|
|
|
//
|
2022-06-05 21:43:38 +00:00
|
|
|
//go:generate callbackgen -type ActiveOrderBook
|
|
|
|
type ActiveOrderBook struct {
|
2023-01-10 12:15:51 +00:00
|
|
|
Symbol string
|
|
|
|
orders *types.SyncOrderMap
|
|
|
|
|
|
|
|
newCallbacks []func(o types.Order)
|
2022-12-04 03:39:43 +00:00
|
|
|
filledCallbacks []func(o types.Order)
|
|
|
|
canceledCallbacks []func(o types.Order)
|
2022-11-02 04:42:09 +00:00
|
|
|
|
2023-02-17 11:15:00 +00:00
|
|
|
pendingOrderUpdates *types.SyncOrderMap
|
|
|
|
|
2022-11-02 04:42:09 +00:00
|
|
|
// sig is the order update signal
|
|
|
|
// this signal will be emitted when a new order is added or removed.
|
|
|
|
C sigchan.Chan
|
2023-08-17 09:16:27 +00:00
|
|
|
|
|
|
|
mu sync.Mutex
|
2020-11-02 14:14:01 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 22:57:25 +00:00
|
|
|
func NewActiveOrderBook(symbol string) *ActiveOrderBook {
|
2022-06-05 21:43:38 +00:00
|
|
|
return &ActiveOrderBook{
|
2023-02-17 11:15:00 +00:00
|
|
|
Symbol: symbol,
|
|
|
|
orders: types.NewSyncOrderMap(),
|
|
|
|
pendingOrderUpdates: types.NewSyncOrderMap(),
|
|
|
|
C: sigchan.New(1),
|
2020-11-02 14:14:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) MarshalJSON() ([]byte, error) {
|
2021-03-17 17:15:06 +00:00
|
|
|
orders := b.Backup()
|
|
|
|
return json.Marshal(orders)
|
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) Backup() []types.SubmitOrder {
|
2022-06-05 10:12:26 +00:00
|
|
|
return b.orders.Backup()
|
2021-03-15 10:25:36 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) BindStream(stream types.Stream) {
|
2020-11-11 15:18:53 +00:00
|
|
|
stream.OnOrderUpdate(b.orderUpdateHandler)
|
|
|
|
}
|
|
|
|
|
2023-09-17 09:42:17 +00:00
|
|
|
func (b *ActiveOrderBook) waitClear(
|
|
|
|
ctx context.Context, order types.Order, waitTime, timeout time.Duration,
|
|
|
|
) (bool, error) {
|
2023-08-17 09:16:27 +00:00
|
|
|
if !b.orders.Exists(order.OrderID) {
|
2022-09-01 04:09:03 +00:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
timeoutC := time.After(timeout)
|
|
|
|
for {
|
2022-11-02 04:55:13 +00:00
|
|
|
select {
|
|
|
|
case <-time.After(waitTime):
|
|
|
|
case <-b.C:
|
|
|
|
}
|
|
|
|
|
2023-08-17 09:16:27 +00:00
|
|
|
clear := !b.orders.Exists(order.OrderID)
|
2022-11-02 04:55:13 +00:00
|
|
|
|
2022-09-01 04:09:03 +00:00
|
|
|
select {
|
|
|
|
case <-timeoutC:
|
|
|
|
return clear, nil
|
|
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
return clear, ctx.Err()
|
|
|
|
|
|
|
|
default:
|
|
|
|
if clear {
|
|
|
|
return clear, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-02 04:55:13 +00:00
|
|
|
// waitAllClear waits for the order book be clear (meaning every order is removed)
|
|
|
|
// if err != nil, it's the context error.
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) waitAllClear(ctx context.Context, waitTime, timeout time.Duration) (bool, error) {
|
2022-11-02 04:55:13 +00:00
|
|
|
clear := b.NumOfOrders() == 0
|
2022-01-15 17:34:28 +00:00
|
|
|
if clear {
|
|
|
|
return clear, nil
|
|
|
|
}
|
|
|
|
|
2022-01-14 16:17:52 +00:00
|
|
|
timeoutC := time.After(timeout)
|
|
|
|
for {
|
2022-11-02 04:55:13 +00:00
|
|
|
select {
|
|
|
|
case <-time.After(waitTime):
|
|
|
|
case <-b.C:
|
|
|
|
}
|
|
|
|
|
|
|
|
// update clear flag
|
|
|
|
clear = b.NumOfOrders() == 0
|
|
|
|
|
2022-01-14 16:17:52 +00:00
|
|
|
select {
|
|
|
|
case <-timeoutC:
|
|
|
|
return clear, nil
|
|
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
return clear, ctx.Err()
|
|
|
|
|
|
|
|
default:
|
|
|
|
if clear {
|
|
|
|
return clear, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-02 04:27:36 +00:00
|
|
|
// FastCancel cancels the orders without verification
|
|
|
|
// It calls the exchange cancel order api and then remove the orders from the active orderbook directly.
|
2022-11-02 04:25:34 +00:00
|
|
|
func (b *ActiveOrderBook) FastCancel(ctx context.Context, ex types.Exchange, orders ...types.Order) error {
|
2022-10-17 04:38:58 +00:00
|
|
|
// if no orders are given, set to cancelAll
|
2023-08-04 10:02:24 +00:00
|
|
|
hasSymbol := b.Symbol != ""
|
2022-10-17 04:38:58 +00:00
|
|
|
if len(orders) == 0 {
|
|
|
|
orders = b.Orders()
|
|
|
|
} else {
|
|
|
|
// simple check on given input
|
|
|
|
for _, o := range orders {
|
2023-08-04 10:02:24 +00:00
|
|
|
if hasSymbol && o.Symbol != b.Symbol {
|
2022-11-02 04:27:36 +00:00
|
|
|
return errors.New("[ActiveOrderBook] cancel " + b.Symbol + " orderbook with different order symbol: " + o.Symbol)
|
2022-10-17 04:38:58 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-01 04:09:03 +00:00
|
|
|
}
|
2022-11-02 04:27:36 +00:00
|
|
|
|
2022-09-01 04:09:03 +00:00
|
|
|
// optimize order cancel for back-testing
|
|
|
|
if IsBackTesting {
|
2022-10-17 04:38:58 +00:00
|
|
|
return ex.CancelOrders(context.Background(), orders...)
|
2022-09-01 04:09:03 +00:00
|
|
|
}
|
2022-11-02 04:25:34 +00:00
|
|
|
|
2022-10-17 04:38:58 +00:00
|
|
|
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)
|
|
|
|
}
|
2022-11-02 04:25:34 +00:00
|
|
|
|
2022-10-17 04:38:58 +00:00
|
|
|
for _, o := range orders {
|
2023-08-17 09:16:27 +00:00
|
|
|
b.orders.Remove(o.OrderID)
|
2022-09-01 04:09:03 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-06 16:10:40 +00:00
|
|
|
// GracefulCancel cancels the active orders gracefully
|
2022-10-17 04:38:58 +00:00
|
|
|
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
|
2023-08-04 10:02:24 +00:00
|
|
|
hasSymbol := b.Symbol != ""
|
2022-10-17 04:38:58 +00:00
|
|
|
for _, o := range orders {
|
2023-08-04 10:02:24 +00:00
|
|
|
if hasSymbol && o.Symbol != b.Symbol {
|
2022-10-17 04:38:58 +00:00
|
|
|
return errors.New("[ActiveOrderBook] cancel " + b.Symbol + " orderbook with different symbol: " + o.Symbol)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-20 17:12:16 +00:00
|
|
|
// optimize order cancel for back-testing
|
|
|
|
if IsBackTesting {
|
|
|
|
return ex.CancelOrders(context.Background(), orders...)
|
|
|
|
}
|
2022-06-09 17:21:59 +00:00
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
log.Debugf("[ActiveOrderBook] gracefully cancelling %s orders...", b.Symbol)
|
2022-06-20 17:12:16 +00:00
|
|
|
waitTime := CancelOrderWaitTime
|
2022-01-06 16:10:40 +00:00
|
|
|
|
2022-01-14 16:17:52 +00:00
|
|
|
startTime := time.Now()
|
2023-12-06 10:01:30 +00:00
|
|
|
// ensure every order is canceled
|
2022-01-14 16:17:52 +00:00
|
|
|
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.
|
2022-01-15 20:40:50 +00:00
|
|
|
// time.Sleep(SentOrderWaitTime)
|
2022-01-14 16:49:27 +00:00
|
|
|
|
|
|
|
// since ctx might be canceled, we should use background context here
|
|
|
|
if err := ex.CancelOrders(context.Background(), orders...); err != nil {
|
2022-06-05 21:43:38 +00:00
|
|
|
log.WithError(err).Errorf("[ActiveOrderBook] can not cancel %s orders", b.Symbol)
|
2022-01-06 16:10:40 +00:00
|
|
|
}
|
|
|
|
|
2022-06-09 17:21:59 +00:00
|
|
|
log.Debugf("[ActiveOrderBook] waiting %s for %s orders to be cancelled...", waitTime, b.Symbol)
|
2022-01-06 16:10:40 +00:00
|
|
|
|
2022-06-09 17:21:59 +00:00
|
|
|
clear, err := b.waitAllClear(ctx, waitTime, 5*time.Second)
|
2022-01-14 16:17:52 +00:00
|
|
|
if clear || err != nil {
|
|
|
|
break
|
2022-01-06 16:10:40 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
log.Warnf("[ActiveOrderBook] %d %s orders are not cancelled yet:", b.NumOfOrders(), b.Symbol)
|
2022-01-14 16:17:52 +00:00
|
|
|
b.Print()
|
2022-01-06 16:10:40 +00:00
|
|
|
|
2022-01-14 16:17:52 +00:00
|
|
|
// verify the current open orders via the RESTful API
|
2022-06-05 21:43:38 +00:00
|
|
|
log.Warnf("[ActiveOrderBook] using REStful API to verify active orders...")
|
2022-08-04 13:12:58 +00:00
|
|
|
|
2023-12-06 10:01:30 +00:00
|
|
|
var symbolOrdersMap = map[string]types.OrderSlice{}
|
2022-08-04 13:12:58 +00:00
|
|
|
for _, order := range orders {
|
2023-12-06 10:01:30 +00:00
|
|
|
symbolOrdersMap[order.Symbol] = append(symbolOrdersMap[order.Symbol], order)
|
2022-01-14 16:17:52 +00:00
|
|
|
}
|
2023-12-06 10:01:30 +00:00
|
|
|
|
2022-10-17 04:38:58 +00:00
|
|
|
var leftOrders []types.Order
|
2023-12-06 10:01:30 +00:00
|
|
|
for symbol := range symbolOrdersMap {
|
|
|
|
symbolOrders, ok := symbolOrdersMap[symbol]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2022-01-06 16:10:40 +00:00
|
|
|
|
2022-08-04 13:12:58 +00:00
|
|
|
openOrders, err := ex.QueryOpenOrders(ctx, symbol)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Errorf("can not query %s open orders", symbol)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-12-06 09:55:25 +00:00
|
|
|
orderMap := types.NewOrderMap(openOrders...)
|
2023-12-06 10:01:30 +00:00
|
|
|
for _, o := range symbolOrders {
|
2022-08-04 13:12:58 +00:00
|
|
|
// if it's not on the order book (open orders), we should remove it from our local side
|
2023-12-06 09:55:25 +00:00
|
|
|
if !orderMap.Exists(o.OrderID) {
|
2022-08-04 13:12:58 +00:00
|
|
|
b.Remove(o)
|
2022-10-17 04:38:58 +00:00
|
|
|
} else {
|
|
|
|
leftOrders = append(leftOrders, o)
|
2022-08-04 13:12:58 +00:00
|
|
|
}
|
2022-01-06 16:10:40 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-04 10:02:24 +00:00
|
|
|
|
2023-12-06 10:01:30 +00:00
|
|
|
// update order slice for the next try
|
2022-10-17 04:38:58 +00:00
|
|
|
orders = leftOrders
|
2022-01-06 16:10:40 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
log.Debugf("[ActiveOrderBook] all %s orders are cancelled successfully in %s", b.Symbol, time.Since(startTime))
|
2022-01-06 16:10:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) orderUpdateHandler(order types.Order) {
|
2023-08-17 08:26:06 +00:00
|
|
|
b.Update(order)
|
2020-11-11 15:18:53 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) Print() {
|
2022-12-23 10:19:00 +00:00
|
|
|
orders := b.orders.Orders()
|
|
|
|
|
|
|
|
// sort orders by price
|
|
|
|
sort.Slice(orders, func(i, j int) bool {
|
|
|
|
o1 := orders[i]
|
|
|
|
o2 := orders[j]
|
|
|
|
return o1.Price.Compare(o2.Price) > 0
|
|
|
|
})
|
|
|
|
|
|
|
|
for _, o := range orders {
|
2022-06-05 10:12:26 +00:00
|
|
|
log.Infof("%s", o)
|
2020-11-02 14:14:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-17 08:26:06 +00:00
|
|
|
// Update updates the order by the order status and emit the related events.
|
|
|
|
// When order is filled, the order will be removed from the internal order storage.
|
|
|
|
// When order is New or PartiallyFilled, the internal order will be updated according to the latest order update.
|
|
|
|
// When the order is cancelled, it will be removed from the internal order storage.
|
|
|
|
func (b *ActiveOrderBook) Update(order types.Order) {
|
2023-08-17 08:28:42 +00:00
|
|
|
hasSymbol := len(b.Symbol) > 0
|
|
|
|
if hasSymbol && order.Symbol != b.Symbol {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-08-17 09:16:27 +00:00
|
|
|
b.mu.Lock()
|
2023-08-17 08:28:42 +00:00
|
|
|
if !b.orders.Exists(order.OrderID) {
|
2023-11-14 08:15:29 +00:00
|
|
|
log.Debugf("[ActiveOrderBook] order #%d %s does not exist, adding it to pending order update", order.OrderID, order.Status)
|
2023-08-17 08:28:42 +00:00
|
|
|
b.pendingOrderUpdates.Add(order)
|
2023-08-17 09:16:27 +00:00
|
|
|
b.mu.Unlock()
|
2023-08-17 08:28:42 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-08-17 09:31:24 +00:00
|
|
|
// if order update time is too old, skip it
|
|
|
|
if previousOrder, ok := b.orders.Get(order.OrderID); ok {
|
2023-11-25 05:22:03 +00:00
|
|
|
// the arguments ordering is important here
|
|
|
|
// if we can't detect which is newer, isNewerOrderUpdate returns false
|
|
|
|
// if you pass two same objects to isNewerOrderUpdate, it returns false
|
|
|
|
if !isNewerOrderUpdate(order, previousOrder) {
|
2023-09-17 10:29:14 +00:00
|
|
|
log.Infof("[ActiveOrderBook] order #%d updateTime %s is out of date, skip it", order.OrderID, order.UpdateTime)
|
2023-08-17 09:31:24 +00:00
|
|
|
b.mu.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-17 08:26:06 +00:00
|
|
|
switch order.Status {
|
|
|
|
case types.OrderStatusFilled:
|
|
|
|
// make sure we have the order and we remove it
|
2023-08-17 09:16:27 +00:00
|
|
|
removed := b.orders.Remove(order.OrderID)
|
|
|
|
b.mu.Unlock()
|
|
|
|
|
|
|
|
if removed {
|
2023-09-17 10:25:21 +00:00
|
|
|
log.Infof("[ActiveOrderBook] order #%d is filled: %s", order.OrderID, order.String())
|
2023-08-17 08:26:06 +00:00
|
|
|
b.EmitFilled(order)
|
|
|
|
}
|
|
|
|
b.C.Emit()
|
|
|
|
|
|
|
|
case types.OrderStatusPartiallyFilled:
|
2023-08-17 09:31:24 +00:00
|
|
|
b.orders.Update(order)
|
2023-08-17 09:16:27 +00:00
|
|
|
b.mu.Unlock()
|
2023-08-17 08:26:06 +00:00
|
|
|
|
|
|
|
case types.OrderStatusNew:
|
2023-08-17 09:31:24 +00:00
|
|
|
b.orders.Update(order)
|
2023-08-17 09:16:27 +00:00
|
|
|
b.mu.Unlock()
|
|
|
|
|
2023-08-17 08:26:06 +00:00
|
|
|
b.C.Emit()
|
|
|
|
|
|
|
|
case types.OrderStatusCanceled, types.OrderStatusRejected:
|
|
|
|
// TODO: note that orders transit to "canceled" may have partially filled
|
2023-08-17 09:16:27 +00:00
|
|
|
log.Debugf("[ActiveOrderBook] order is %s, removing order %s", order.Status, order)
|
|
|
|
b.orders.Remove(order.OrderID)
|
|
|
|
b.mu.Unlock()
|
|
|
|
|
2023-08-17 08:26:06 +00:00
|
|
|
if order.Status == types.OrderStatusCanceled {
|
|
|
|
b.EmitCanceled(order)
|
|
|
|
}
|
|
|
|
b.C.Emit()
|
|
|
|
|
|
|
|
default:
|
2023-08-17 09:16:27 +00:00
|
|
|
b.mu.Unlock()
|
2023-08-17 08:26:06 +00:00
|
|
|
log.Warnf("[ActiveOrderBook] unhandled order status: %s", order.Status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) Add(orders ...types.Order) {
|
2022-06-05 10:12:26 +00:00
|
|
|
hasSymbol := len(b.Symbol) > 0
|
2023-08-04 10:02:24 +00:00
|
|
|
|
2020-11-02 14:14:01 +00:00
|
|
|
for _, order := range orders {
|
2023-02-17 11:24:08 +00:00
|
|
|
if hasSymbol && b.Symbol != order.Symbol {
|
|
|
|
continue
|
2023-02-17 11:15:00 +00:00
|
|
|
}
|
2023-02-17 11:24:08 +00:00
|
|
|
|
|
|
|
b.add(order)
|
2023-02-17 11:15:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-22 09:44:41 +00:00
|
|
|
func isNewerOrderUpdate(a, b types.Order) bool {
|
2023-09-17 10:20:29 +00:00
|
|
|
// compare state first
|
|
|
|
switch a.Status {
|
|
|
|
|
|
|
|
case types.OrderStatusCanceled, types.OrderStatusRejected: // canceled is a final state
|
|
|
|
switch b.Status {
|
|
|
|
case types.OrderStatusNew, types.OrderStatusPartiallyFilled:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
case types.OrderStatusPartiallyFilled:
|
|
|
|
switch b.Status {
|
|
|
|
case types.OrderStatusNew:
|
|
|
|
return true
|
2023-11-22 09:34:26 +00:00
|
|
|
case types.OrderStatusPartiallyFilled:
|
|
|
|
// unknown for equal
|
|
|
|
if a.ExecutedQuantity.Compare(b.ExecutedQuantity) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-09-17 10:20:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case types.OrderStatusFilled:
|
|
|
|
switch b.Status {
|
|
|
|
case types.OrderStatusFilled, types.OrderStatusPartiallyFilled, types.OrderStatusNew:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-22 09:44:41 +00:00
|
|
|
return isNewerOrderUpdateTime(a, b)
|
2023-11-22 09:43:38 +00:00
|
|
|
}
|
|
|
|
|
2023-11-22 09:44:41 +00:00
|
|
|
func isNewerOrderUpdateTime(a, b types.Order) bool {
|
2023-09-17 10:20:29 +00:00
|
|
|
au := time.Time(a.UpdateTime)
|
|
|
|
bu := time.Time(b.UpdateTime)
|
|
|
|
|
|
|
|
if !au.IsZero() && !bu.IsZero() && au.After(bu) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !au.IsZero() && bu.IsZero() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-02-17 11:15:00 +00:00
|
|
|
// add the order to the active order book and check the pending order
|
|
|
|
func (b *ActiveOrderBook) add(order types.Order) {
|
|
|
|
if pendingOrder, ok := b.pendingOrderUpdates.Get(order.OrderID); ok {
|
2023-02-23 10:08:21 +00:00
|
|
|
// if the pending order update time is newer than the adding order
|
|
|
|
// we should use the pending order rather than the adding order.
|
2023-12-11 09:59:02 +00:00
|
|
|
// if the pending order is older, then we should add the new one, and drop the pending order
|
|
|
|
log.Debugf("found pending order update: %+v", pendingOrder)
|
2023-11-22 09:44:41 +00:00
|
|
|
if isNewerOrderUpdate(pendingOrder, order) {
|
2023-12-11 09:59:02 +00:00
|
|
|
log.Infof("pending order update is newer: %+v", pendingOrder)
|
2023-02-23 10:08:21 +00:00
|
|
|
order = pendingOrder
|
|
|
|
}
|
|
|
|
|
|
|
|
b.orders.Add(order)
|
|
|
|
b.pendingOrderUpdates.Remove(pendingOrder.OrderID)
|
|
|
|
|
|
|
|
// when using add(order), it's usually a new maker order on the order book.
|
|
|
|
// so, when it's not status=new, we should trigger order update handler
|
|
|
|
if order.Status != types.OrderStatusNew {
|
|
|
|
// emit the order update handle function to trigger callback
|
2023-08-17 09:16:27 +00:00
|
|
|
b.Update(order)
|
2020-11-02 14:14:01 +00:00
|
|
|
}
|
2023-02-17 11:50:46 +00:00
|
|
|
|
2023-02-17 11:15:00 +00:00
|
|
|
} else {
|
|
|
|
b.orders.Add(order)
|
2020-11-02 14:14:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) Exists(order types.Order) bool {
|
2023-08-17 09:16:27 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-06-05 10:12:26 +00:00
|
|
|
return b.orders.Exists(order.OrderID)
|
2021-05-22 09:44:07 +00:00
|
|
|
}
|
|
|
|
|
2023-02-17 11:50:46 +00:00
|
|
|
func (b *ActiveOrderBook) Get(orderID uint64) (types.Order, bool) {
|
|
|
|
return b.orders.Get(orderID)
|
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) Remove(order types.Order) bool {
|
2023-08-17 09:16:27 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-06-05 10:12:26 +00:00
|
|
|
return b.orders.Remove(order.OrderID)
|
2020-11-02 14:14:01 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) NumOfOrders() int {
|
2022-06-05 10:12:26 +00:00
|
|
|
return b.orders.Len()
|
2021-05-13 11:41:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 21:43:38 +00:00
|
|
|
func (b *ActiveOrderBook) Orders() types.OrderSlice {
|
2022-06-05 10:12:26 +00:00
|
|
|
return b.orders.Orders()
|
2020-11-02 14:14:01 +00:00
|
|
|
}
|
2022-12-20 09:33:53 +00:00
|
|
|
|
|
|
|
func (b *ActiveOrderBook) Lookup(f func(o types.Order) bool) *types.Order {
|
|
|
|
return b.orders.Lookup(f)
|
|
|
|
}
|