2023-10-02 03:56:15 +00:00
|
|
|
package grid2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2023-10-11 09:12:11 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/bbgo"
|
2023-10-02 03:56:15 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/exchange/retry"
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
"github.com/c9s/bbgo/pkg/util"
|
2023-10-13 08:50:59 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"go.uber.org/multierr"
|
2023-10-02 03:56:15 +00:00
|
|
|
)
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
type SyncActiveOrdersOpts struct {
|
|
|
|
logger *logrus.Entry
|
|
|
|
metricsLabels prometheus.Labels
|
|
|
|
activeOrderBook *bbgo.ActiveOrderBook
|
|
|
|
orderQueryService types.ExchangeOrderQueryService
|
|
|
|
exchange types.Exchange
|
|
|
|
}
|
|
|
|
|
2023-10-16 08:02:43 +00:00
|
|
|
func (s *Strategy) initializeRecoverCh() bool {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
2023-10-17 08:13:05 +00:00
|
|
|
isInitialize := false
|
2023-10-16 08:02:43 +00:00
|
|
|
|
2023-10-17 08:13:05 +00:00
|
|
|
if s.activeOrdersRecoverC == nil {
|
|
|
|
s.logger.Info("initializing recover channel")
|
|
|
|
s.activeOrdersRecoverC = make(chan struct{}, 1)
|
2023-10-16 08:02:43 +00:00
|
|
|
} else {
|
2023-10-17 08:13:05 +00:00
|
|
|
s.logger.Info("recover channel is already initialized, trigger active orders recover")
|
|
|
|
isInitialize = true
|
|
|
|
|
|
|
|
select {
|
|
|
|
case s.activeOrdersRecoverC <- struct{}{}:
|
|
|
|
s.logger.Info("trigger active orders recover")
|
|
|
|
default:
|
|
|
|
s.logger.Info("activeOrdersRecoverC is full")
|
|
|
|
}
|
2023-10-16 08:02:43 +00:00
|
|
|
}
|
|
|
|
|
2023-10-17 08:13:05 +00:00
|
|
|
return isInitialize
|
2023-10-16 08:02:43 +00:00
|
|
|
}
|
|
|
|
|
2023-10-09 09:06:22 +00:00
|
|
|
func (s *Strategy) recoverActiveOrdersPeriodically(ctx context.Context) {
|
2023-10-17 08:13:05 +00:00
|
|
|
// every time we activeOrdersRecoverC receive signal, do active orders recover
|
|
|
|
if isInitialize := s.initializeRecoverCh(); isInitialize {
|
2023-10-16 08:02:43 +00:00
|
|
|
return
|
|
|
|
}
|
2023-10-02 03:56:15 +00:00
|
|
|
|
2023-10-11 09:12:11 +00:00
|
|
|
// make ticker's interval random in 25 min ~ 35 min
|
|
|
|
interval := util.MillisecondsJitter(25*time.Minute, 10*60*1000)
|
2023-10-06 10:04:57 +00:00
|
|
|
s.logger.Infof("[ActiveOrderRecover] interval: %s", interval)
|
|
|
|
ticker := time.NewTicker(interval)
|
2023-10-02 03:56:15 +00:00
|
|
|
defer ticker.Stop()
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
opts := SyncActiveOrdersOpts{
|
|
|
|
logger: s.logger,
|
|
|
|
metricsLabels: s.newPrometheusLabels(),
|
|
|
|
activeOrderBook: s.orderExecutor.ActiveMakerOrders(),
|
|
|
|
orderQueryService: s.orderQueryService,
|
|
|
|
exchange: s.session.Exchange,
|
|
|
|
}
|
|
|
|
|
2023-10-02 03:56:15 +00:00
|
|
|
for {
|
|
|
|
select {
|
2023-10-09 09:06:22 +00:00
|
|
|
|
2023-10-02 03:56:15 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
|
2023-10-09 09:06:22 +00:00
|
|
|
case <-ticker.C:
|
2023-10-13 08:50:59 +00:00
|
|
|
if err := syncActiveOrders(ctx, opts); err != nil {
|
2023-10-09 09:06:22 +00:00
|
|
|
log.WithError(err).Errorf("unable to sync active orders")
|
|
|
|
}
|
|
|
|
|
2023-10-17 08:13:05 +00:00
|
|
|
case <-s.activeOrdersRecoverC:
|
2023-10-13 08:50:59 +00:00
|
|
|
if err := syncActiveOrders(ctx, opts); err != nil {
|
2023-10-09 09:06:22 +00:00
|
|
|
log.WithError(err).Errorf("unable to sync active orders")
|
|
|
|
}
|
|
|
|
|
2023-10-06 10:04:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
func syncActiveOrders(ctx context.Context, opts SyncActiveOrdersOpts) error {
|
|
|
|
opts.logger.Infof("[ActiveOrderRecover] syncActiveOrders")
|
2023-10-09 09:09:41 +00:00
|
|
|
|
2023-10-12 06:20:34 +00:00
|
|
|
notAddNonExistingOpenOrdersAfter := time.Now().Add(-5 * time.Minute)
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
openOrders, err := retry.QueryOpenOrdersUntilSuccessful(ctx, opts.exchange, opts.activeOrderBook.Symbol)
|
2023-10-09 09:06:22 +00:00
|
|
|
if err != nil {
|
2023-10-13 08:50:59 +00:00
|
|
|
opts.logger.WithError(err).Error("[ActiveOrderRecover] failed to query open orders, skip this time")
|
|
|
|
return errors.Wrapf(err, "[ActiveOrderRecover] failed to query open orders, skip this time")
|
2023-10-09 09:06:22 +00:00
|
|
|
}
|
|
|
|
|
2023-10-17 07:20:28 +00:00
|
|
|
if metricsNumOfOpenOrders != nil {
|
|
|
|
metricsNumOfOpenOrders.With(opts.metricsLabels).Set(float64(len(openOrders)))
|
|
|
|
}
|
2023-10-11 09:36:18 +00:00
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
activeOrders := opts.activeOrderBook.Orders()
|
2023-10-02 03:56:15 +00:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
var errs error
|
2023-10-02 03:56:15 +00:00
|
|
|
// update active orders not in open orders
|
|
|
|
for _, activeOrder := range activeOrders {
|
2023-10-09 09:10:11 +00:00
|
|
|
if _, exist := openOrdersMap[activeOrder.OrderID]; exist {
|
2023-10-12 06:20:34 +00:00
|
|
|
// no need to sync active order already in active orderbook, because we only need to know if it filled or not.
|
2023-10-09 09:10:11 +00:00
|
|
|
delete(openOrdersMap, activeOrder.OrderID)
|
|
|
|
} else {
|
2023-10-13 08:50:59 +00:00
|
|
|
opts.logger.Infof("found active order #%d is not in the open orders, updating...", activeOrder.OrderID)
|
2023-10-09 09:06:22 +00:00
|
|
|
|
2023-10-17 05:51:51 +00:00
|
|
|
// sleep 100ms to avoid DDOS
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
if err := syncActiveOrder(ctx, opts.activeOrderBook, opts.orderQueryService, activeOrder.OrderID); err != nil {
|
|
|
|
opts.logger.WithError(err).Errorf("[ActiveOrderRecover] unable to query order #%d", activeOrder.OrderID)
|
|
|
|
errs = multierr.Append(errs, err)
|
2023-10-02 03:56:15 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update open orders not in active orders
|
2023-10-06 10:04:57 +00:00
|
|
|
for _, openOrder := range openOrdersMap {
|
2023-10-12 06:20:34 +00:00
|
|
|
// we don't add open orders into active orderbook if updated in 5 min
|
|
|
|
if openOrder.UpdateTime.After(notAddNonExistingOpenOrdersAfter) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
opts.activeOrderBook.Add(openOrder)
|
|
|
|
// opts.activeOrderBook.Update(openOrder)
|
2023-10-02 03:56:15 +00:00
|
|
|
}
|
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
return errs
|
2023-10-02 03:56:15 +00:00
|
|
|
}
|
2023-10-11 09:12:11 +00:00
|
|
|
|
2023-10-13 08:50:59 +00:00
|
|
|
func syncActiveOrder(ctx context.Context, activeOrderBook *bbgo.ActiveOrderBook, orderQueryService types.ExchangeOrderQueryService, orderID uint64) error {
|
|
|
|
updatedOrder, err := retry.QueryOrderUntilSuccessful(ctx, orderQueryService, types.OrderQuery{
|
|
|
|
Symbol: activeOrderBook.Symbol,
|
2023-10-11 09:12:11 +00:00
|
|
|
OrderID: strconv.FormatUint(orderID, 10),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
activeOrderBook.Update(*updatedOrder)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|