mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
Merge pull request #1368 from c9s/feature/grid2/merge-recover
FEATURE: merge grid recover and active orders recover logic
This commit is contained in:
commit
be4c69c365
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
"github.com/c9s/bbgo/pkg/exchange/retry"
|
"github.com/c9s/bbgo/pkg/exchange/retry"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -42,6 +43,201 @@ import (
|
||||||
4. run grid recover to make sure all the twin price has its order.
|
4. run grid recover to make sure all the twin price has its order.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
func (s *Strategy) recover(ctx context.Context) error {
|
||||||
|
historyService, implemented := s.session.Exchange.(types.ExchangeTradeHistoryService)
|
||||||
|
// if the exchange doesn't support ExchangeTradeHistoryService, do not run recover
|
||||||
|
if !implemented {
|
||||||
|
s.logger.Warn("ExchangeTradeHistoryService is not implemented, can not recover grid")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
activeOrderBook := s.orderExecutor.ActiveMakerOrders()
|
||||||
|
activeOrders := activeOrderBook.Orders()
|
||||||
|
|
||||||
|
openOrders, err := retry.QueryOpenOrdersUntilSuccessful(ctx, s.session.Exchange, s.Symbol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it's new strategy or need to recover
|
||||||
|
if len(activeOrders) == 0 && len(openOrders) == 0 && s.GridProfitStats.InitialOrderID == 0 {
|
||||||
|
// even though there is no open orders and initial orderID is 0
|
||||||
|
// we still need to query trades to make sure if we need to recover or not
|
||||||
|
trades, err := historyService.QueryTrades(ctx, s.Symbol, &types.TradeQueryOptions{
|
||||||
|
// from 1, because some API will ignore 0 last trade id
|
||||||
|
LastTradeID: 1,
|
||||||
|
// if there is any trades, we need to recover.
|
||||||
|
Limit: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to query trades when recovering")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(trades) == 0 {
|
||||||
|
s.logger.Info("no open order, no active order, no trade, it's a new strategy so no need to recover")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("start recovering")
|
||||||
|
|
||||||
|
if s.getGrid() == nil {
|
||||||
|
s.setGrid(s.newGrid())
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
pins := s.getGrid().Pins
|
||||||
|
|
||||||
|
activeOrdersInTwinOrderBook, err := buildTwinOrderBook(pins, activeOrders)
|
||||||
|
openOrdersInTwinOrderBook, err := buildTwinOrderBook(pins, openOrders)
|
||||||
|
|
||||||
|
s.logger.Infof("active orders' twin orderbook\n%s", activeOrdersInTwinOrderBook.String())
|
||||||
|
s.logger.Infof("open orders in twin orderbook\n%s", openOrdersInTwinOrderBook.String())
|
||||||
|
|
||||||
|
// remove index 0, because twin orderbook's price is from the second one
|
||||||
|
pins = pins[1:]
|
||||||
|
var noTwinOrderPins []fixedpoint.Value
|
||||||
|
|
||||||
|
for _, pin := range pins {
|
||||||
|
v := fixedpoint.Value(pin)
|
||||||
|
activeOrder := activeOrdersInTwinOrderBook.GetTwinOrder(v)
|
||||||
|
openOrder := openOrdersInTwinOrderBook.GetTwinOrder(v)
|
||||||
|
if activeOrder == nil || openOrder == nil {
|
||||||
|
return fmt.Errorf("there is no any twin order at this pin, can not recover")
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeOrderID uint64 = 0
|
||||||
|
if activeOrder.Exist() {
|
||||||
|
activeOrderID = activeOrder.GetOrder().OrderID
|
||||||
|
}
|
||||||
|
|
||||||
|
var openOrderID uint64 = 0
|
||||||
|
if openOrder.Exist() {
|
||||||
|
openOrderID = openOrder.GetOrder().OrderID
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 3
|
||||||
|
if activeOrderID == 0 && openOrderID == 0 {
|
||||||
|
noTwinOrderPins = append(noTwinOrderPins, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 1
|
||||||
|
if activeOrderID == 0 {
|
||||||
|
activeOrderBook.Add(openOrder.GetOrder())
|
||||||
|
// also add open orders into active order's twin orderbook, we will use this active orderbook to recover empty price grid
|
||||||
|
activeOrdersInTwinOrderBook.AddTwinOrder(v, openOrder)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 2
|
||||||
|
if openOrderID == 0 {
|
||||||
|
syncActiveOrder(ctx, activeOrderBook, s.orderQueryService, activeOrder.GetOrder().OrderID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 4
|
||||||
|
if activeOrderID != openOrderID {
|
||||||
|
return fmt.Errorf("there are two different orders in the same pin, can not recover")
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 5
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Infof("twin orderbook after adding open orders\n%s", activeOrdersInTwinOrderBook.String())
|
||||||
|
|
||||||
|
if len(noTwinOrderPins) != 0 {
|
||||||
|
if err := s.recoverEmptyGridOnTwinOrderBook(ctx, activeOrdersInTwinOrderBook, historyService, s.orderQueryService); err != nil {
|
||||||
|
s.logger.WithError(err).Error("failed to recover empty grid")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Infof("twin orderbook after recovering no twin order on grid\n%s", activeOrdersInTwinOrderBook.String())
|
||||||
|
|
||||||
|
if activeOrdersInTwinOrderBook.EmptyTwinOrderSize() > 0 {
|
||||||
|
return fmt.Errorf("there is still empty grid in twin orderbook")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pin := range noTwinOrderPins {
|
||||||
|
twinOrder := activeOrdersInTwinOrderBook.GetTwinOrder(pin)
|
||||||
|
if twinOrder == nil {
|
||||||
|
return fmt.Errorf("should not get nil twin order after recovering empty grid, check it")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !twinOrder.Exist() {
|
||||||
|
return fmt.Errorf("should not get empty twin order after recovering empty grid, check it")
|
||||||
|
}
|
||||||
|
|
||||||
|
activeOrderBook.EmitFilled(twinOrder.GetOrder())
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do not emit ready here, emit ready only once when opening grid or recovering grid after worker stopped
|
||||||
|
// s.EmitGridReady()
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
debugGrid(s.logger, s.grid, s.orderExecutor.ActiveMakerOrders())
|
||||||
|
|
||||||
|
bbgo.Sync(ctx, s)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) recoverEmptyGridOnTwinOrderBook(
|
||||||
|
ctx context.Context,
|
||||||
|
twinOrderBook *TwinOrderBook,
|
||||||
|
queryTradesService types.ExchangeTradeHistoryService,
|
||||||
|
queryOrderService types.ExchangeOrderQueryService,
|
||||||
|
) error {
|
||||||
|
if twinOrderBook.EmptyTwinOrderSize() == 0 {
|
||||||
|
s.logger.Info("no empty grid")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
existedOrders := twinOrderBook.SyncOrderMap()
|
||||||
|
|
||||||
|
until := time.Now()
|
||||||
|
since := until.Add(-1 * time.Hour)
|
||||||
|
// hard limit for recover
|
||||||
|
recoverSinceLimit := time.Date(2023, time.March, 10, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
if s.RecoverGridWithin != 0 && until.Add(-1*s.RecoverGridWithin).After(recoverSinceLimit) {
|
||||||
|
recoverSinceLimit = until.Add(-1 * s.RecoverGridWithin)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := queryTradesToUpdateTwinOrderBook(ctx, s.Symbol, twinOrderBook, queryTradesService, queryOrderService, existedOrders, since, until, s.debugLog); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to query trades to update twin orderbook")
|
||||||
|
}
|
||||||
|
|
||||||
|
until = since
|
||||||
|
since = until.Add(-6 * time.Hour)
|
||||||
|
|
||||||
|
if twinOrderBook.EmptyTwinOrderSize() == 0 {
|
||||||
|
s.logger.Infof("stop querying trades because there is no empty twin order on twin orderbook")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.GridProfitStats != nil && s.GridProfitStats.Since != nil && until.Before(*s.GridProfitStats.Since) {
|
||||||
|
s.logger.Infof("stop querying trades because the time range is out of the strategy's since (%s)", *s.GridProfitStats.Since)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if until.Before(recoverSinceLimit) {
|
||||||
|
s.logger.Infof("stop querying trades because the time range is out of the limit (%s)", recoverSinceLimit)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func buildTwinOrderBook(pins []Pin, orders []types.Order) (*TwinOrderBook, error) {
|
func buildTwinOrderBook(pins []Pin, orders []types.Order) (*TwinOrderBook, error) {
|
||||||
book := newTwinOrderBook(pins)
|
book := newTwinOrderBook(pins)
|
||||||
|
|
||||||
|
@ -77,7 +273,8 @@ func queryTradesToUpdateTwinOrderBook(
|
||||||
queryOrderService types.ExchangeOrderQueryService,
|
queryOrderService types.ExchangeOrderQueryService,
|
||||||
existedOrders *types.SyncOrderMap,
|
existedOrders *types.SyncOrderMap,
|
||||||
since, until time.Time,
|
since, until time.Time,
|
||||||
logger func(format string, args ...interface{})) error {
|
logger func(format string, args ...interface{}),
|
||||||
|
) error {
|
||||||
if twinOrderBook == nil {
|
if twinOrderBook == nil {
|
||||||
return fmt.Errorf("twin orderbook should not be nil, please check it")
|
return fmt.Errorf("twin orderbook should not be nil, please check it")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user