mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +00:00
Merge pull request #1114 from c9s/feature/grids/recover-from-trades
FEATURE: verify the grids before emit filled orders
This commit is contained in:
commit
50c626dad9
|
@ -1393,10 +1393,12 @@ func (s *Strategy) recoverGridWithOpenOrdersByScanningTrades(ctx context.Context
|
|||
|
||||
expectedOrderNums := s.GridNum - 1
|
||||
openOrdersOnGridNums := int64(len(openOrdersOnGrid))
|
||||
s.logger.Infof("[DEBUG] open orders nums: %d, expected nums: %d", openOrdersOnGridNums, expectedOrderNums)
|
||||
s.logger.Debugf("open orders nums: %d, expected nums: %d", openOrdersOnGridNums, expectedOrderNums)
|
||||
if expectedOrderNums == openOrdersOnGridNums {
|
||||
// no need to recover
|
||||
return nil
|
||||
} else if expectedOrderNums < openOrdersOnGridNums {
|
||||
return fmt.Errorf("amount of grid's open orders should not > amount of expected grid's orders")
|
||||
}
|
||||
|
||||
// 1. build pin-order map
|
||||
|
@ -1413,28 +1415,34 @@ func (s *Strategy) recoverGridWithOpenOrdersByScanningTrades(ctx context.Context
|
|||
|
||||
// 3. get the filled orders from pin-order map
|
||||
filledOrders := pinOrdersFilled.AscendingOrders()
|
||||
numsFilledOrders := len(filledOrders)
|
||||
i := 0
|
||||
if numsFilledOrders == int(expectedOrderNums-openOrdersOnGridNums) {
|
||||
numFilledOrders := len(filledOrders)
|
||||
if numFilledOrders == 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 if numFilledOrders == int(expectedOrderNums-openOrdersOnGridNums+1) {
|
||||
filledOrders = filledOrders[1:]
|
||||
} else {
|
||||
return fmt.Errorf("not reasonable num of filled orders")
|
||||
}
|
||||
|
||||
// 4. emit the filled orders
|
||||
// 4. verify the grid
|
||||
if err := s.verifyFilledGrid(s.grid.Pins, pinOrdersOpen, filledOrders); err != nil {
|
||||
return errors.Wrapf(err, "verify grid with error")
|
||||
}
|
||||
|
||||
s.debugOrders("emit filled orders", filledOrders)
|
||||
|
||||
// 5. 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)
|
||||
for _, filledOrder := range filledOrders {
|
||||
activeOrderBook.EmitFilled(filledOrder)
|
||||
}
|
||||
|
||||
// 5. emit grid ready
|
||||
// 6. emit grid ready
|
||||
s.EmitGridReady()
|
||||
|
||||
// 6. debug and send metrics
|
||||
// 7. debug and send metrics
|
||||
// wait for the reverse order to be placed
|
||||
time.Sleep(2 * time.Second)
|
||||
debugGrid(s.logger, grid, s.orderExecutor.ActiveMakerOrders())
|
||||
s.updateGridNumOfOrdersMetricsWithLock()
|
||||
s.updateOpenOrderPricesMetrics(s.orderExecutor.ActiveMakerOrders().Orders())
|
||||
|
@ -1442,6 +1450,56 @@ func (s *Strategy) recoverGridWithOpenOrdersByScanningTrades(ctx context.Context
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) verifyFilledGrid(pins []Pin, pinOrders PinOrderMap, filledOrders []types.Order) error {
|
||||
s.logger.Debugf("pins: %+v", pins)
|
||||
s.logger.Debugf("open pin orders: %+v", pinOrders)
|
||||
s.debugOrders("filled orders", filledOrders)
|
||||
|
||||
for _, filledOrder := range filledOrders {
|
||||
price := s.Market.FormatPrice(filledOrder.Price)
|
||||
if o, exist := pinOrders[price]; !exist {
|
||||
return fmt.Errorf("the price (%s) is not in pins", price)
|
||||
} else if o.OrderID != 0 {
|
||||
return fmt.Errorf("there is already an order at this price (%s)", price)
|
||||
} else {
|
||||
pinOrders[price] = filledOrder
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Debugf("filled pin orders: %+v", pinOrders)
|
||||
|
||||
side := types.SideTypeBuy
|
||||
for _, pin := range pins {
|
||||
price := s.Market.FormatPrice(fixedpoint.Value(pin))
|
||||
order, exist := pinOrders[price]
|
||||
if !exist {
|
||||
return fmt.Errorf("there is no order at price (%s)", price)
|
||||
}
|
||||
|
||||
// if there is order with OrderID = 0, means we hit the empty pin
|
||||
// there must be only one empty pin in the grid
|
||||
// all orders below this pin need to be bid orders, above this pin need to be ask orders
|
||||
if order.OrderID == 0 {
|
||||
if side == types.SideTypeBuy {
|
||||
side = types.SideTypeSell
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("not only one empty order in this grid")
|
||||
}
|
||||
|
||||
if order.Side != side {
|
||||
return fmt.Errorf("the side is wrong")
|
||||
}
|
||||
}
|
||||
|
||||
if side != types.SideTypeSell {
|
||||
return fmt.Errorf("there is no empty pin in the grid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildPinOrderMap build the pin-order map with grid and open orders.
|
||||
// The keys of this map contains all required pins of this grid.
|
||||
// If the Order of the pin is empty types.Order (OrderID == 0), it means there is no open orders at this pin.
|
||||
|
@ -1492,9 +1550,10 @@ func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, history
|
|||
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))
|
||||
s.logger.Debugf("QueryTrades return %d trades", len(trades))
|
||||
|
||||
for _, trade := range trades {
|
||||
s.logger.Debugf(trade.String())
|
||||
if existedOrders.Exists(trade.OrderID) {
|
||||
// already queries, skip
|
||||
continue
|
||||
|
@ -1508,8 +1567,7 @@ func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, history
|
|||
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())
|
||||
s.logger.Debugf("%s (group_id: %d)", order.String(), order.GroupID)
|
||||
|
||||
// avoid query this order again
|
||||
existedOrders.Add(*order)
|
||||
|
@ -2083,9 +2141,11 @@ func (s *Strategy) startProcess(ctx context.Context, session *bbgo.ExchangeSessi
|
|||
|
||||
func (s *Strategy) recoverGrid(ctx context.Context, session *bbgo.ExchangeSession) error {
|
||||
if s.RecoverGridByScanningTrades {
|
||||
s.logger.Debugf("recover grid by scanning trades")
|
||||
return s.recoverGridByScanningTrades(ctx, session)
|
||||
}
|
||||
|
||||
s.logger.Debugf("recover grid by scanning orders")
|
||||
return s.recoverGridByScanningOrders(ctx, session)
|
||||
}
|
||||
|
||||
|
@ -2119,7 +2179,7 @@ func (s *Strategy) recoverGridByScanningOrders(ctx context.Context, session *bbg
|
|||
func (s *Strategy) recoverGridByScanningTrades(ctx context.Context, session *bbgo.ExchangeSession) error {
|
||||
// no initial order id means we don't need to recover
|
||||
if s.GridProfitStats.InitialOrderID == 0 {
|
||||
s.logger.Info("[DEBUG] new strategy, no need to recover")
|
||||
s.logger.Debug("new strategy, no need to recover")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2130,11 +2190,10 @@ func (s *Strategy) recoverGridByScanningTrades(ctx context.Context, session *bbg
|
|||
|
||||
s.logger.Infof("found %d open orders left on the %s order book", len(openOrders), s.Symbol)
|
||||
|
||||
s.logger.Infof("[DEBUG] recover grid with group id: %d", s.OrderGroupID)
|
||||
s.logger.Debugf("recover grid with group id: %d", s.OrderGroupID)
|
||||
// filter out the order with the group id belongs to this grid
|
||||
var openOrdersOnGrid []types.Order
|
||||
for _, order := range openOrders {
|
||||
s.logger.Infof("[DEBUG] order (%d) group id: %d", order.OrderID, order.GroupID)
|
||||
if order.GroupID == s.OrderGroupID {
|
||||
openOrdersOnGrid = append(openOrdersOnGrid, order)
|
||||
}
|
||||
|
|
|
@ -1209,3 +1209,214 @@ func Test_getOrdersFromPinOrderMapInAscOrder(t *testing.T) {
|
|||
assert.Equal(uint64(1), orders[1].OrderID)
|
||||
assert.Equal(uint64(3), orders[2].OrderID)
|
||||
}
|
||||
|
||||
func Test_verifyFilledGrid(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
s := newTestStrategy()
|
||||
s.UpperPrice = number(400.0)
|
||||
s.LowerPrice = number(100.0)
|
||||
s.GridNum = 4
|
||||
s.grid = s.newGrid()
|
||||
|
||||
t.Run("valid grid with buy/sell orders", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{
|
||||
OrderID: 1,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"200.00": types.Order{},
|
||||
"300.00": types.Order{
|
||||
OrderID: 3,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"400.00": types.Order{
|
||||
OrderID: 4,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
t.Run("valid grid with only buy orders", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{
|
||||
OrderID: 1,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"200.00": types.Order{
|
||||
OrderID: 2,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"300.00": types.Order{
|
||||
OrderID: 3,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"400.00": types.Order{},
|
||||
}
|
||||
|
||||
assert.NoError(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
t.Run("valid grid with only sell orders", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{},
|
||||
"200.00": types.Order{
|
||||
OrderID: 2,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"300.00": types.Order{
|
||||
OrderID: 3,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"400.00": types.Order{
|
||||
OrderID: 4,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
t.Run("invalid grid with multiple empty pins", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{
|
||||
OrderID: 1,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"200.00": types.Order{},
|
||||
"300.00": types.Order{},
|
||||
"400.00": types.Order{
|
||||
OrderID: 4,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Error(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
t.Run("invalid grid without empty pin", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{
|
||||
OrderID: 1,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"200.00": types.Order{
|
||||
OrderID: 2,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"300.00": types.Order{
|
||||
OrderID: 3,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"400.00": types.Order{
|
||||
OrderID: 4,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Error(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
t.Run("invalid grid with Buy-empty-Sell-Buy order", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{
|
||||
OrderID: 1,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"200.00": types.Order{},
|
||||
"300.00": types.Order{
|
||||
OrderID: 3,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"400.00": types.Order{
|
||||
OrderID: 4,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Error(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
t.Run("invalid grid with Sell-empty order", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{
|
||||
OrderID: 1,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"200.00": types.Order{
|
||||
OrderID: 2,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"300.00": types.Order{
|
||||
OrderID: 3,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
"400.00": types.Order{},
|
||||
}
|
||||
|
||||
assert.Error(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
t.Run("invalid grid with empty-Buy order", func(t *testing.T) {
|
||||
pinOrderMap := PinOrderMap{
|
||||
"100.00": types.Order{},
|
||||
"200.00": types.Order{
|
||||
OrderID: 2,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"300.00": types.Order{
|
||||
OrderID: 3,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
"400.00": types.Order{
|
||||
OrderID: 4,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Error(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
|
||||
})
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user