Skip to content

Commit 03e25f1

Browse files
authored
Merge pull request #1060 from c9s/fix/active-order-book-pending-order-update
fix: process pending order update for active order book
2 parents 21cdb7a + 4dc4f73 commit 03e25f1

File tree

4 files changed

+133
-6
lines changed

4 files changed

+133
-6
lines changed

pkg/bbgo/activeorderbook.go

+37-6
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@ type ActiveOrderBook struct {
2525
filledCallbacks []func(o types.Order)
2626
canceledCallbacks []func(o types.Order)
2727

28+
pendingOrderUpdates *types.SyncOrderMap
29+
2830
// sig is the order update signal
2931
// this signal will be emitted when a new order is added or removed.
3032
C sigchan.Chan
3133
}
3234

3335
func NewActiveOrderBook(symbol string) *ActiveOrderBook {
3436
return &ActiveOrderBook{
35-
Symbol: symbol,
36-
orders: types.NewSyncOrderMap(),
37-
C: sigchan.New(1),
37+
Symbol: symbol,
38+
orders: types.NewSyncOrderMap(),
39+
pendingOrderUpdates: types.NewSyncOrderMap(),
40+
C: sigchan.New(1),
3841
}
3942
}
4043

@@ -230,6 +233,11 @@ func (b *ActiveOrderBook) orderUpdateHandler(order types.Order) {
230233
return
231234
}
232235

236+
if !b.orders.Exists(order.OrderID) {
237+
b.pendingOrderUpdates.Add(order)
238+
return
239+
}
240+
233241
switch order.Status {
234242
case types.OrderStatusFilled:
235243
// make sure we have the order and we remove it
@@ -277,25 +285,48 @@ func (b *ActiveOrderBook) Print() {
277285
func (b *ActiveOrderBook) Update(orders ...types.Order) {
278286
hasSymbol := len(b.Symbol) > 0
279287
for _, order := range orders {
280-
if hasSymbol && b.Symbol == order.Symbol {
281-
b.orders.Update(order)
288+
if hasSymbol && b.Symbol != order.Symbol {
289+
continue
282290
}
291+
292+
b.orders.Update(order)
283293
}
284294
}
285295

286296
func (b *ActiveOrderBook) Add(orders ...types.Order) {
287297
hasSymbol := len(b.Symbol) > 0
288298
for _, order := range orders {
289-
if hasSymbol && b.Symbol == order.Symbol {
299+
if hasSymbol && b.Symbol != order.Symbol {
300+
continue
301+
}
302+
303+
b.add(order)
304+
}
305+
}
306+
307+
// add the order to the active order book and check the pending order
308+
func (b *ActiveOrderBook) add(order types.Order) {
309+
if pendingOrder, ok := b.pendingOrderUpdates.Get(order.OrderID); ok {
310+
if pendingOrder.UpdateTime.Time().After(order.UpdateTime.Time()) {
311+
b.orders.Add(pendingOrder)
312+
} else {
290313
b.orders.Add(order)
291314
}
315+
316+
b.pendingOrderUpdates.Remove(order.OrderID)
317+
} else {
318+
b.orders.Add(order)
292319
}
293320
}
294321

295322
func (b *ActiveOrderBook) Exists(order types.Order) bool {
296323
return b.orders.Exists(order.OrderID)
297324
}
298325

326+
func (b *ActiveOrderBook) Get(orderID uint64) (types.Order, bool) {
327+
return b.orders.Get(orderID)
328+
}
329+
299330
func (b *ActiveOrderBook) Remove(order types.Order) bool {
300331
return b.orders.Remove(order.OrderID)
301332
}

pkg/bbgo/activeorderbook_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package bbgo
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/c9s/bbgo/pkg/fixedpoint"
10+
"github.com/c9s/bbgo/pkg/types"
11+
)
12+
13+
func TestActiveOrderBook_pendingOrders(t *testing.T) {
14+
now := time.Now()
15+
ob := NewActiveOrderBook("")
16+
17+
// if we received filled order first
18+
// should be added to pending orders
19+
ob.orderUpdateHandler(types.Order{
20+
OrderID: 99,
21+
SubmitOrder: types.SubmitOrder{
22+
Symbol: "BTCUSDT",
23+
Side: types.SideTypeBuy,
24+
Type: types.OrderTypeLimit,
25+
Quantity: number(0.01),
26+
Price: number(19000.0),
27+
AveragePrice: fixedpoint.Zero,
28+
StopPrice: fixedpoint.Zero,
29+
},
30+
Status: types.OrderStatusFilled,
31+
CreationTime: types.Time(now),
32+
UpdateTime: types.Time(now),
33+
})
34+
35+
assert.Len(t, ob.pendingOrderUpdates.Orders(), 1)
36+
37+
// should be added to pending orders
38+
ob.Add(types.Order{
39+
OrderID: 99,
40+
SubmitOrder: types.SubmitOrder{
41+
Symbol: "BTCUSDT",
42+
Side: types.SideTypeBuy,
43+
Type: types.OrderTypeLimit,
44+
Quantity: number(0.01),
45+
Price: number(19000.0),
46+
AveragePrice: fixedpoint.Zero,
47+
StopPrice: fixedpoint.Zero,
48+
},
49+
Status: types.OrderStatusNew,
50+
CreationTime: types.Time(now),
51+
UpdateTime: types.Time(now.Add(-time.Second)),
52+
})
53+
54+
o99, ok := ob.Get(99)
55+
assert.True(t, ok)
56+
assert.Equal(t, types.OrderStatusFilled, o99.Status)
57+
}

pkg/bbgo/order_execution.go

+27
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,33 @@ func BatchRetryPlaceOrder(ctx context.Context, exchange types.Exchange, errIdx [
6262
return createdOrders, err
6363
}
6464

65+
// BatchPlaceOrderChan post orders with a channel, the created order will be sent to this channel immediately, so that
66+
// the caller can add the created order to the active order book or the order store to collect trades.
67+
// this method is used when you have large amount of orders to be sent and most of the orders might be filled as taker order.
68+
// channel orderC will be closed when all the submit orders are submitted.
69+
func BatchPlaceOrderChan(ctx context.Context, exchange types.Exchange, orderC chan types.Order, submitOrders ...types.SubmitOrder) (types.OrderSlice, []int, error) {
70+
defer close(orderC)
71+
72+
var createdOrders types.OrderSlice
73+
var err error
74+
var errIndexes []int
75+
for i, submitOrder := range submitOrders {
76+
createdOrder, err2 := exchange.SubmitOrder(ctx, submitOrder)
77+
if err2 != nil {
78+
err = multierr.Append(err, err2)
79+
errIndexes = append(errIndexes, i)
80+
} else if createdOrder != nil {
81+
createdOrder.Tag = submitOrder.Tag
82+
83+
orderC <- *createdOrder
84+
85+
createdOrders = append(createdOrders, *createdOrder)
86+
}
87+
}
88+
89+
return createdOrders, errIndexes, err
90+
}
91+
6592
// BatchPlaceOrder
6693
func BatchPlaceOrder(ctx context.Context, exchange types.Exchange, submitOrders ...types.SubmitOrder) (types.OrderSlice, []int, error) {
6794
var createdOrders types.OrderSlice

pkg/types/ordermap.go

+12
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ func (m OrderMap) Exists(orderID uint64) bool {
5656
return ok
5757
}
5858

59+
func (m OrderMap) Get(orderID uint64) (Order, bool) {
60+
order, ok := m[orderID]
61+
return order, ok
62+
}
63+
5964
func (m OrderMap) FindByStatus(status OrderStatus) (orders OrderSlice) {
6065
for _, o := range m {
6166
if o.Status == status {
@@ -165,6 +170,13 @@ func (m *SyncOrderMap) Exists(orderID uint64) (exists bool) {
165170
return exists
166171
}
167172

173+
func (m *SyncOrderMap) Get(orderID uint64) (Order, bool) {
174+
m.Lock()
175+
order, ok := m.orders.Get(orderID)
176+
m.Unlock()
177+
return order, ok
178+
}
179+
168180
func (m *SyncOrderMap) Lookup(f func(o Order) bool) *Order {
169181
m.Lock()
170182
defer m.Unlock()

0 commit comments

Comments
 (0)