2023-10-02 03:56:15 +00:00
|
|
|
package grid2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"strconv"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/exchange/retry"
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
"github.com/c9s/bbgo/pkg/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s *Strategy) recoverActiveOrdersWithOpenOrdersPeriodically(ctx context.Context) {
|
2023-10-06 10:04:57 +00:00
|
|
|
// every time we activeOrdersRecoverCh receive signal, do active orders recover
|
|
|
|
s.activeOrdersRecoverCh = make(chan struct{}, 1)
|
2023-10-02 03:56:15 +00:00
|
|
|
|
2023-10-06 10:04:57 +00:00
|
|
|
// make ticker's interval random in 40 min ~ 70 min
|
|
|
|
interval := util.MillisecondsJitter(40*time.Minute, 30*60*1000)
|
|
|
|
s.logger.Infof("[ActiveOrderRecover] interval: %s", interval)
|
|
|
|
ticker := time.NewTicker(interval)
|
2023-10-02 03:56:15 +00:00
|
|
|
defer ticker.Stop()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2023-10-06 10:04:57 +00:00
|
|
|
s.queryOpenOrdersThenRecoverActiveOrders(ctx)
|
|
|
|
case <-s.activeOrdersRecoverCh:
|
|
|
|
s.queryOpenOrdersThenRecoverActiveOrders(ctx)
|
2023-10-02 03:56:15 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-06 10:04:57 +00:00
|
|
|
func (s *Strategy) queryOpenOrdersThenRecoverActiveOrders(ctx context.Context) {
|
|
|
|
if openOrders, err := retry.QueryOpenOrdersUntilSuccessful(ctx, s.session.Exchange, s.Symbol); err != nil {
|
|
|
|
s.logger.WithError(err).Error("[ActiveOrderRecover] failed to query open orders, skip this time")
|
|
|
|
} else {
|
|
|
|
if err := s.recoverActiveOrdersWithOpenOrders(ctx, openOrders); err != nil {
|
|
|
|
s.logger.WithError(err).Error("[ActiveOrderRecover] failed to recover avtive orderbook")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-02 03:56:15 +00:00
|
|
|
func (s *Strategy) recoverActiveOrdersWithOpenOrders(ctx context.Context, openOrders []types.Order) error {
|
|
|
|
recovered := atomic.LoadInt32(&s.recovered)
|
|
|
|
if recovered == 0 {
|
|
|
|
s.logger.Infof("[ActiveOrderRecover] skip recovering active orders because recover not ready")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
s.logger.Infof("[ActiveOrderRecover] recovering active orders with open orders")
|
|
|
|
|
|
|
|
if s.getGrid() == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
|
|
|
activeOrderBook := s.orderExecutor.ActiveMakerOrders()
|
|
|
|
activeOrders := activeOrderBook.Orders()
|
|
|
|
|
|
|
|
openOrdersMap := make(map[uint64]types.Order)
|
|
|
|
for _, openOrder := range openOrders {
|
2023-10-06 10:04:57 +00:00
|
|
|
openOrdersMap[openOrder.OrderID] = openOrder
|
2023-10-02 03:56:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// update active orders not in open orders
|
|
|
|
for _, activeOrder := range activeOrders {
|
|
|
|
if _, exist := openOrdersMap[activeOrder.OrderID]; !exist {
|
|
|
|
s.logger.Infof("find active order (%d) not in open orders, updating...", activeOrder.OrderID)
|
|
|
|
updatedOrder, err := retry.QueryOrderUntilSuccessful(ctx, s.orderQueryService, types.OrderQuery{
|
|
|
|
Symbol: activeOrder.Symbol,
|
|
|
|
OrderID: strconv.FormatUint(activeOrder.OrderID, 10),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
s.logger.WithError(err).Errorf("[ActiveOrderRecover] unable to query order (%d)", activeOrder.OrderID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
activeOrderBook.Update(*updatedOrder)
|
2023-10-06 10:04:57 +00:00
|
|
|
} else {
|
|
|
|
delete(openOrdersMap, activeOrder.OrderID)
|
2023-10-02 03:56:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-06 10:04:57 +00:00
|
|
|
// TODO: should we add open orders back into active orderbook ?
|
2023-10-02 03:56:15 +00:00
|
|
|
// update open orders not in active orders
|
2023-10-06 10:04:57 +00:00
|
|
|
for _, openOrder := range openOrdersMap {
|
|
|
|
activeOrderBook.Update(openOrder)
|
2023-10-02 03:56:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|