mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 16:55:15 +00:00
FEATURE: recover grids with open orders by querying trades process and its buildPinOrderMap method
This commit is contained in:
parent
6a6d7a6293
commit
4288c82e25
|
@ -1339,6 +1339,70 @@ func (s *Strategy) checkMinimalQuoteInvestment(grid *Grid) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) recoverGridWithOpenOrdersByScanningTrades(ctx context.Context, historyService types.ExchangeTradeHistoryService, openOrdersOnGrid []types.Order) error {
|
||||||
|
if s.orderQueryService == nil {
|
||||||
|
return fmt.Errorf("orderQueryService is nil, it can't get orders by trade")
|
||||||
|
}
|
||||||
|
|
||||||
|
// set grid
|
||||||
|
grid := s.newGrid()
|
||||||
|
s.setGrid(grid)
|
||||||
|
|
||||||
|
// add open orders to active order book
|
||||||
|
s.addOrdersToActiveOrderBook(openOrdersOnGrid)
|
||||||
|
|
||||||
|
expectedOrderNums := s.GridNum - 1
|
||||||
|
openOrdersOnGridNums := int64(len(openOrdersOnGrid))
|
||||||
|
s.logger.Infof("[DEBUG] open orders nums: %d, expected nums: %d", openOrdersOnGridNums, expectedOrderNums)
|
||||||
|
if expectedOrderNums == openOrdersOnGridNums {
|
||||||
|
// no need to recover
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. build pin-order map
|
||||||
|
// 2. fill the pin-order map by querying trades
|
||||||
|
// 3. get the filled orders from pin-order map
|
||||||
|
// 4. emit the filled orders
|
||||||
|
|
||||||
|
// 5. emit grid ready
|
||||||
|
s.EmitGridReady()
|
||||||
|
|
||||||
|
// 6. debug and send metrics
|
||||||
|
debugGrid(s.logger, grid, s.orderExecutor.ActiveMakerOrders())
|
||||||
|
s.updateGridNumOfOrdersMetrics()
|
||||||
|
s.updateOpenOrderPricesMetrics(s.orderExecutor.ActiveMakerOrders().Orders())
|
||||||
|
|
||||||
|
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.
|
||||||
|
func (s *Strategy) buildPinOrderMap(grid *Grid, openOrders []types.Order) (map[string]types.Order, error) {
|
||||||
|
pinOrderMap := make(map[string]types.Order)
|
||||||
|
|
||||||
|
for _, pin := range grid.Pins {
|
||||||
|
priceStr := s.FormatPrice(fixedpoint.Value(pin))
|
||||||
|
pinOrderMap[priceStr] = types.Order{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, openOrder := range openOrders {
|
||||||
|
priceStr := s.FormatPrice(openOrder.Price)
|
||||||
|
v, exist := pinOrderMap[priceStr]
|
||||||
|
if !exist {
|
||||||
|
return nil, fmt.Errorf("the price of the order (id: %d) is not in pins", openOrder.OrderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.OrderID != 0 {
|
||||||
|
return nil, fmt.Errorf("there are duplicated open orders at the same pin")
|
||||||
|
}
|
||||||
|
|
||||||
|
pinOrderMap[priceStr] = openOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
return pinOrderMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Strategy) recoverGridWithOpenOrders(ctx context.Context, historyService types.ExchangeTradeHistoryService, openOrders []types.Order) error {
|
func (s *Strategy) recoverGridWithOpenOrders(ctx context.Context, historyService types.ExchangeTradeHistoryService, openOrders []types.Order) error {
|
||||||
grid := s.newGrid()
|
grid := s.newGrid()
|
||||||
|
|
||||||
|
@ -1846,7 +1910,7 @@ func (s *Strategy) startProcess(ctx context.Context, session *bbgo.ExchangeSessi
|
||||||
if s.RecoverOrdersWhenStart {
|
if s.RecoverOrdersWhenStart {
|
||||||
// do recover only when triggerPrice is not set and not in the back-test mode
|
// do recover only when triggerPrice is not set and not in the back-test mode
|
||||||
s.logger.Infof("recoverWhenStart is set, trying to recover grid orders...")
|
s.logger.Infof("recoverWhenStart is set, trying to recover grid orders...")
|
||||||
if err := s.recoverGrid(ctx, session); err != nil {
|
if err := s.recoverGridByScanningOrders(ctx, session); err != nil {
|
||||||
s.logger.WithError(err).Errorf("recover error")
|
s.logger.WithError(err).Errorf("recover error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1857,7 +1921,7 @@ func (s *Strategy) startProcess(ctx context.Context, session *bbgo.ExchangeSessi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) recoverGrid(ctx context.Context, session *bbgo.ExchangeSession) error {
|
func (s *Strategy) recoverGridByScanningOrders(ctx context.Context, session *bbgo.ExchangeSession) error {
|
||||||
openOrders, err := session.Exchange.QueryOpenOrders(ctx, s.Symbol)
|
openOrders, err := session.Exchange.QueryOpenOrders(ctx, s.Symbol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1884,6 +1948,45 @@ func (s *Strategy) recoverGrid(ctx context.Context, session *bbgo.ExchangeSessio
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
openOrders, err := session.Exchange.QueryOpenOrders(ctx, s.Symbol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Infof("found %d open orders belong to this grid on the %s order book", len(openOrdersOnGrid), s.Symbol)
|
||||||
|
|
||||||
|
historyService, implemented := session.Exchange.(types.ExchangeTradeHistoryService)
|
||||||
|
if !implemented {
|
||||||
|
s.logger.Warn("ExchangeTradeHistoryService is not implemented, can not recover grid")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.recoverGridWithOpenOrdersByScanningTrades(ctx, historyService, openOrdersOnGrid); err != nil {
|
||||||
|
return errors.Wrap(err, "grid recover error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// openOrdersMismatches verifies if the open orders are on the grid pins
|
// openOrdersMismatches verifies if the open orders are on the grid pins
|
||||||
// return true if mismatches
|
// return true if mismatches
|
||||||
func (s *Strategy) openOrdersMismatches(ctx context.Context, session *bbgo.ExchangeSession) (bool, error) {
|
func (s *Strategy) openOrdersMismatches(ctx context.Context, session *bbgo.ExchangeSession) (bool, error) {
|
||||||
|
|
|
@ -981,3 +981,118 @@ func Test_roundUpMarketQuantity(t *testing.T) {
|
||||||
assert.Equal(t, "0.00000003", q3.String(), "rounding prec 8")
|
assert.Equal(t, "0.00000003", q3.String(), "rounding prec 8")
|
||||||
assert.Equal(t, 8, prec)
|
assert.Equal(t, 8, prec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_buildPinOrderMap(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
s := newTestStrategy()
|
||||||
|
s.UpperPrice = number(2000.0)
|
||||||
|
s.LowerPrice = number(1000.0)
|
||||||
|
s.GridNum = 11
|
||||||
|
s.grid = s.newGrid()
|
||||||
|
|
||||||
|
t.Run("successful case", func(t *testing.T) {
|
||||||
|
openOrders := []types.Order{
|
||||||
|
types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
Symbol: s.Symbol,
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Type: types.OrderTypeLimit,
|
||||||
|
Quantity: number(1.0),
|
||||||
|
Price: number(1000.0),
|
||||||
|
AveragePrice: number(0),
|
||||||
|
StopPrice: number(0),
|
||||||
|
Market: s.Market,
|
||||||
|
TimeInForce: types.TimeInForceGTC,
|
||||||
|
},
|
||||||
|
Exchange: "max",
|
||||||
|
GID: 1,
|
||||||
|
OrderID: 1,
|
||||||
|
Status: types.OrderStatusNew,
|
||||||
|
ExecutedQuantity: number(0.0),
|
||||||
|
IsWorking: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
m, err := s.buildPinOrderMap(s.grid, openOrders)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Len(m, 11)
|
||||||
|
|
||||||
|
for pin, order := range m {
|
||||||
|
if pin == s.FormatPrice(openOrders[0].Price) {
|
||||||
|
assert.Equal(openOrders[0].OrderID, order.OrderID)
|
||||||
|
} else {
|
||||||
|
assert.Equal(uint64(0), order.OrderID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("there is one order with non-pin price in openOrders", func(t *testing.T) {
|
||||||
|
openOrders := []types.Order{
|
||||||
|
types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
Symbol: s.Symbol,
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Type: types.OrderTypeLimit,
|
||||||
|
Quantity: number(1.0),
|
||||||
|
Price: number(1111.0),
|
||||||
|
AveragePrice: number(0),
|
||||||
|
StopPrice: number(0),
|
||||||
|
Market: s.Market,
|
||||||
|
TimeInForce: types.TimeInForceGTC,
|
||||||
|
},
|
||||||
|
Exchange: "max",
|
||||||
|
GID: 1,
|
||||||
|
OrderID: 1,
|
||||||
|
Status: types.OrderStatusNew,
|
||||||
|
ExecutedQuantity: number(0.0),
|
||||||
|
IsWorking: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := s.buildPinOrderMap(s.grid, openOrders)
|
||||||
|
assert.Error(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("there are duplicated open orders at same pin", func(t *testing.T) {
|
||||||
|
openOrders := []types.Order{
|
||||||
|
types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
Symbol: s.Symbol,
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Type: types.OrderTypeLimit,
|
||||||
|
Quantity: number(1.0),
|
||||||
|
Price: number(1000.0),
|
||||||
|
AveragePrice: number(0),
|
||||||
|
StopPrice: number(0),
|
||||||
|
Market: s.Market,
|
||||||
|
TimeInForce: types.TimeInForceGTC,
|
||||||
|
},
|
||||||
|
Exchange: "max",
|
||||||
|
GID: 1,
|
||||||
|
OrderID: 1,
|
||||||
|
Status: types.OrderStatusNew,
|
||||||
|
ExecutedQuantity: number(0.0),
|
||||||
|
IsWorking: false,
|
||||||
|
},
|
||||||
|
types.Order{
|
||||||
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
Symbol: s.Symbol,
|
||||||
|
Side: types.SideTypeBuy,
|
||||||
|
Type: types.OrderTypeLimit,
|
||||||
|
Quantity: number(1.0),
|
||||||
|
Price: number(1000.0),
|
||||||
|
AveragePrice: number(0),
|
||||||
|
StopPrice: number(0),
|
||||||
|
Market: s.Market,
|
||||||
|
TimeInForce: types.TimeInForceGTC,
|
||||||
|
},
|
||||||
|
Exchange: "max",
|
||||||
|
GID: 2,
|
||||||
|
OrderID: 2,
|
||||||
|
Status: types.OrderStatusNew,
|
||||||
|
ExecutedQuantity: number(0.0),
|
||||||
|
IsWorking: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := s.buildPinOrderMap(s.grid, openOrders)
|
||||||
|
assert.Error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user