FEATURE: get filled orders when bbgo down

This commit is contained in:
chiahung 2023-03-10 13:58:04 +08:00
parent 60d7d20ced
commit 7af4e3bf8a
2 changed files with 150 additions and 2 deletions

View File

@ -1400,9 +1400,36 @@ func (s *Strategy) recoverGridWithOpenOrdersByScanningTrades(ctx context.Context
}
// 1. build pin-order map
// 2. fill the pin-order map by querying trades
pinOrderMapWithOpenOrders, err := s.buildPinOrderMap(grid, openOrdersOnGrid)
if err != nil {
return errors.Wrapf(err, "failed to build pin order map with open orders")
}
// 2. build the filled pin-order map by querying trades
pinOrderMapWithFilledOrders, err := s.buildFilledPinOrderMapFromTrades(ctx, historyService, pinOrderMapWithOpenOrders)
if err != nil {
return errors.Wrapf(err, "failed to build filled pin order map")
}
// 3. get the filled orders from pin-order map
filledOrders := getOrdersFromPinOrderMapInAscOrder(pinOrderMapWithFilledOrders)
numsFilledOrders := len(filledOrders)
i := 0
if numsFilledOrders == int(expectedOrderNums-openOrdersOnGridNums) {
// nums of filled order is the same as Size - 1 - num(open orders)
} else if numsFilledOrders == int(expectedOrderNums-openOrdersOnGridNums+1) {
i++
} else {
return fmt.Errorf("not reasonable num of filled orders")
}
// 4. emit the filled orders
activeOrderBook := s.orderExecutor.ActiveMakerOrders()
for ; i < numsFilledOrders; i++ {
filledOrder := filledOrders[i]
s.logger.Infof("[DEBUG] emit filled order: %s (%s)", filledOrder.String(), filledOrder.UpdateTime)
activeOrderBook.EmitFilled(filledOrder)
}
// 5. emit grid ready
s.EmitGridReady()
@ -1443,6 +1470,96 @@ func (s *Strategy) buildPinOrderMap(grid *Grid, openOrders []types.Order) (map[s
return pinOrderMap, nil
}
// buildFilledPinOrderMapFromTrades will query the trades from last 24 hour and use them to build a pin order map
// It will skip the orders on pins at which open orders are already
func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, historyService types.ExchangeTradeHistoryService, openPinOrderMap map[string]types.Order) (map[string]types.Order, error) {
filledPinOrderMap := make(map[string]types.Order)
var limit int64 = 1000
// get the filled orders when bbgo is down in order from trades
// [NOTE] only retrieve from last 24 hours !!!
var fromTradeID uint64 = 0
for {
trades, err := historyService.QueryTrades(ctx, s.Symbol, &types.TradeQueryOptions{
LastTradeID: fromTradeID,
Limit: limit,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to query trades to recover the grid with open orders")
}
s.logger.Infof("[DEBUG] len of trades: %d", len(trades))
for _, trade := range trades {
order, err := s.orderQueryService.QueryOrder(ctx, types.OrderQuery{
OrderID: strconv.FormatUint(trade.OrderID, 10),
})
if err != nil {
return nil, errors.Wrapf(err, "failed to query order by trade")
}
s.logger.Infof("[DEBUG] trade: %s", trade.String())
s.logger.Infof("[DEBUG] (group_id: %d) order: %s", order.GroupID, order.String())
// add 1 to avoid duplicate
fromTradeID = trade.ID + 1
// this trade doesn't belong to this grid
if order.GroupID != s.OrderGroupID {
continue
}
// checked the trade's order is filled order
priceStr := s.FormatPrice(order.Price)
v, exist := openPinOrderMap[priceStr]
if !exist {
return nil, fmt.Errorf("the price of the order with the same GroupID is not in pins")
}
// skip open orders on grid
if v.OrderID != 0 {
continue
}
// check the order's creation time
if pinOrder, exist := filledPinOrderMap[priceStr]; exist && pinOrder.CreationTime.Time().After(order.CreationTime.Time()) {
// do not replace the pin order if the order's creation time is not after pin order's creation time
// this situation should not happen actually, because the trades is already sorted.
s.logger.Infof("pinOrder's creation time (%s) should not be after order's creation time (%s)", pinOrder.CreationTime, order.CreationTime)
continue
}
filledPinOrderMap[priceStr] = *order
}
// stop condition
if int64(len(trades)) < limit {
break
}
}
return filledPinOrderMap, nil
}
// get the orders from pin order map and sort it in asc order
func getOrdersFromPinOrderMapInAscOrder(pinOrderMap map[string]types.Order) []types.Order {
var orders []types.Order
for _, order := range pinOrderMap {
// skip empty order
if order.OrderID == 0 {
continue
}
orders = append(orders, order)
}
sort.Slice(orders, func(i, j int) bool {
return orders[i].UpdateTime.Before(orders[j].UpdateTime.Time())
})
return orders
}
func (s *Strategy) recoverGridWithOpenOrders(ctx context.Context, historyService types.ExchangeTradeHistoryService, openOrders []types.Order) error {
grid := s.newGrid()

View File

@ -6,6 +6,7 @@ import (
"context"
"errors"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/sirupsen/logrus"
@ -1177,4 +1178,34 @@ func Test_buildPinOrderMap(t *testing.T) {
_, err := s.buildPinOrderMap(s.grid, openOrders)
assert.Error(err)
})
}
}
func Test_getOrdersFromPinOrderMapInAscOrder(t *testing.T) {
assert := assert.New(t)
now := time.Now()
pinOrderMap := map[string]types.Order{
"1000": types.Order{
OrderID: 1,
CreationTime: types.Time(now.Add(1 * time.Hour)),
UpdateTime: types.Time(now.Add(5 * time.Hour)),
},
"1100": types.Order{},
"1200": types.Order{},
"1300": types.Order{
OrderID: 3,
CreationTime: types.Time(now.Add(3 * time.Hour)),
UpdateTime: types.Time(now.Add(6 * time.Hour)),
},
"1400": types.Order{
OrderID: 2,
CreationTime: types.Time(now.Add(2 * time.Hour)),
UpdateTime: types.Time(now.Add(4 * time.Hour)),
},
}
orders := getOrdersFromPinOrderMapInAscOrder(pinOrderMap)
assert.Len(orders, 3)
assert.Equal(uint64(2), orders[0].OrderID)
assert.Equal(uint64(1), orders[1].OrderID)
assert.Equal(uint64(3), orders[2].OrderID)
}