bbgo/pkg/strategy/common/sync.go

106 lines
3.6 KiB
Go
Raw Permalink Normal View History

package common
import (
"context"
"fmt"
"strconv"
"time"
"git.qtrade.icu/lychiyu/bbgo/pkg/bbgo"
"git.qtrade.icu/lychiyu/bbgo/pkg/exchange"
maxapi "git.qtrade.icu/lychiyu/bbgo/pkg/exchange/max/maxapi"
"git.qtrade.icu/lychiyu/bbgo/pkg/exchange/retry"
"git.qtrade.icu/lychiyu/bbgo/pkg/types"
"github.com/sirupsen/logrus"
"go.uber.org/multierr"
)
func SyncActiveOrder(ctx context.Context, ex types.Exchange, orderQueryService types.ExchangeOrderQueryService, activeOrderBook *bbgo.ActiveOrderBook, orderID uint64, syncBefore time.Time) (isOrderUpdated bool, err error) {
isMax := exchange.IsMaxExchange(ex)
updatedOrder, err := retry.QueryOrderUntilSuccessful(ctx, orderQueryService, types.OrderQuery{
Symbol: activeOrderBook.Symbol,
OrderID: strconv.FormatUint(orderID, 10),
})
if err != nil {
return isOrderUpdated, err
}
if updatedOrder == nil {
return isOrderUpdated, fmt.Errorf("unexpected error, order object (%d) is a nil pointer, please check common.SyncActiveOrder()", orderID)
}
// maxapi.OrderStateFinalizing does not mean the fee is calculated
// we should only consider order state done for MAX
if isMax && updatedOrder.OriginalStatus != string(maxapi.OrderStateDone) {
return isOrderUpdated, nil
}
// should only trigger order update when the updated time is old enough
isOrderUpdated = updatedOrder.UpdateTime.Before(syncBefore)
if isOrderUpdated {
activeOrderBook.Update(*updatedOrder)
}
return isOrderUpdated, nil
}
type SyncActiveOrdersOpts struct {
Logger *logrus.Entry
Exchange types.Exchange
OrderQueryService types.ExchangeOrderQueryService
ActiveOrderBook *bbgo.ActiveOrderBook
OpenOrders []types.Order
}
func SyncActiveOrders(ctx context.Context, opts SyncActiveOrdersOpts) error {
opts.Logger.Infof("[ActiveOrderRecover] syncActiveOrders")
// only sync orders which is updated over 3 min, because we may receive from websocket and handle it twice
syncBefore := time.Now().Add(-3 * time.Minute)
activeOrders := opts.ActiveOrderBook.Orders()
openOrdersMap := make(map[uint64]types.Order)
for _, openOrder := range opts.OpenOrders {
openOrdersMap[openOrder.OrderID] = openOrder
}
var errs error
// update active orders not in open orders
for _, activeOrder := range activeOrders {
if _, exist := openOrdersMap[activeOrder.OrderID]; exist {
// no need to sync active order already in active orderbook, because we only need to know if it filled or not.
delete(openOrdersMap, activeOrder.OrderID)
} else {
opts.Logger.Infof("[ActiveOrderRecover] found active order #%d is not in the open orders, updating...", activeOrder.OrderID)
isActiveOrderBookUpdated, err := SyncActiveOrder(ctx, opts.Exchange, opts.OrderQueryService, opts.ActiveOrderBook, activeOrder.OrderID, syncBefore)
if err != nil {
opts.Logger.WithError(err).Errorf("[ActiveOrderRecover] unable to query order #%d", activeOrder.OrderID)
errs = multierr.Append(errs, err)
continue
}
if !isActiveOrderBookUpdated {
opts.Logger.Infof("[ActiveOrderRecover] active order #%d is updated in 3 min, skip updating...", activeOrder.OrderID)
}
}
}
// update open orders not in active orders
for _, openOrder := range openOrdersMap {
opts.Logger.Infof("found open order #%d is not in active orderbook, updating...", openOrder.OrderID)
// we don't add open orders into active orderbook if updated in 3 min, because we may receive message from websocket and add it twice.
if openOrder.UpdateTime.After(syncBefore) {
opts.Logger.Infof("open order #%d is updated in 3 min, skip updating...", openOrder.OrderID)
continue
}
opts.ActiveOrderBook.Add(openOrder)
}
return errs
}