FEATURE: get more trades to recover

This commit is contained in:
chiahung 2023-04-19 15:09:44 +08:00
parent 45a27ffe06
commit 7585ed14df
3 changed files with 77 additions and 17 deletions

View File

@ -2,6 +2,7 @@ package grid2
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
@ -41,9 +42,19 @@ func (m PinOrderMap) String() string {
var sb strings.Builder var sb strings.Builder
sb.WriteString("================== PIN ORDER MAP ==================\n") sb.WriteString("================== PIN ORDER MAP ==================\n")
for pin, order := range m { var pins []fixedpoint.Value
sb.WriteString(fmt.Sprintf("%+v -> %s\n", pin, order.String())) for pin, _ := range m {
pins = append(pins, pin)
} }
sort.Slice(pins, func(i, j int) bool {
return pins[j] < pins[i]
})
for _, pin := range pins {
sb.WriteString(fmt.Sprintf("- %8s) %s\n", pin, m[pin].String()))
}
sb.WriteString("================== END OF PIN ORDER MAP ==================\n") sb.WriteString("================== END OF PIN ORDER MAP ==================\n")
return sb.String() return sb.String()
} }

View File

@ -99,8 +99,9 @@ func (s *Strategy) recoverWithOpenOrdersByScanningTrades(ctx context.Context, hi
return errors.Wrapf(err, "failed to build pin order map with open orders") return errors.Wrapf(err, "failed to build pin order map with open orders")
} }
expectedNumOfQueriedOrders := int(expectedNumOfOrders - numGridOpenOrders)
// 2. build the filled pin-order map by querying trades // 2. build the filled pin-order map by querying trades
pinOrdersFilled, err := s.buildFilledPinOrderMapFromTrades(ctx, historyService, pinOrdersOpen) pinOrdersFilled, err := s.buildFilledPinOrderMapFromTrades(ctx, historyService, pinOrdersOpen, expectedNumOfQueriedOrders)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to build filled pin order map") return errors.Wrapf(err, "failed to build filled pin order map")
} }
@ -108,10 +109,10 @@ func (s *Strategy) recoverWithOpenOrdersByScanningTrades(ctx context.Context, hi
// 3. get the filled orders from pin-order map // 3. get the filled orders from pin-order map
filledOrders := pinOrdersFilled.AscendingOrders() filledOrders := pinOrdersFilled.AscendingOrders()
numFilledOrders := len(filledOrders) numFilledOrders := len(filledOrders)
if numFilledOrders == int(expectedNumOfOrders-numGridOpenOrders) { if numFilledOrders == expectedNumOfQueriedOrders {
// nums of filled order is the same as Size - 1 - num(open orders) // nums of filled order is the same as Size - 1 - num(open orders)
s.logger.Infof("nums of filled order is the same as Size - 1 - len(open orders) : %d = %d - 1 - %d", numFilledOrders, s.grid.Size, numGridOpenOrders) s.logger.Infof("nums of filled order is the same as Size - 1 - len(open orders) : %d = %d - 1 - %d", numFilledOrders, s.grid.Size, numGridOpenOrders)
} else if numFilledOrders == int(expectedNumOfOrders-numGridOpenOrders+1) { } else if numFilledOrders == expectedNumOfQueriedOrders+1 {
filledOrders = filledOrders[1:] filledOrders = filledOrders[1:]
} else { } else {
return fmt.Errorf("not reasonable num of filled orders") return fmt.Errorf("not reasonable num of filled orders")
@ -206,30 +207,77 @@ func (s *Strategy) buildPinOrderMap(pins []Pin, openOrders []types.Order) (PinOr
// buildFilledPinOrderMapFromTrades will query the trades from last 24 hour and use them to build a pin order map // buildFilledPinOrderMapFromTrades will query the trades from last 24 hour and use them to build a pin order map
// It will skip the orders on pins at which open orders are already // It will skip the orders on pins at which open orders are already
func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, historyService types.ExchangeTradeHistoryService, pinOrdersOpen PinOrderMap) (PinOrderMap, error) { func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, historyService types.ExchangeTradeHistoryService, pinOrdersOpen PinOrderMap, expectedQueryNums int) (PinOrderMap, error) {
pinOrdersFilled := make(PinOrderMap) pinOrdersFilled := make(PinOrderMap)
// existedOrders is used to avoid re-query the same orders // existedOrders is used to avoid re-query the same orders
existedOrders := pinOrdersOpen.SyncOrderMap() existedOrders := pinOrdersOpen.SyncOrderMap()
var limit int64 = 1000
// get the filled orders when bbgo is down in order from trades // get the filled orders when bbgo is down in order from trades
// [NOTE] only retrieve from last 24 hours !!! until := time.Now()
// the first query only query the last 1 hour, because mostly shutdown and recovery happens within 1 hour
since := until.Add(-1 * time.Hour)
// hard limit for recover
recoverSinceLimit := time.Date(2023, time.March, 10, 0, 0, 0, 0, time.UTC)
// if the RecoverWithin is set and is > hard limit, we need to assign it to recoverSinceLimit
if s.RecoverWithin != 0 && until.Add(-1*s.RecoverWithin).After(recoverSinceLimit) {
recoverSinceLimit = until.Add(-1 * s.RecoverWithin)
}
for {
if err := s.queryTradesToUpdatePinOrdersMap(ctx, historyService, pinOrdersOpen, pinOrdersFilled, existedOrders, since, until); err != nil {
return nil, errors.Wrapf(err, "failed to query trades to update pin orders map")
}
until = since
since = until.Add(-6 * time.Hour)
if len(pinOrdersFilled) >= expectedQueryNums {
s.logger.Infof("stop querying trades because pin orders filled (%d) > expected query nums (%d)", len(pinOrdersFilled), expectedQueryNums)
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 pinOrdersFilled, nil
}
func (s *Strategy) queryTradesToUpdatePinOrdersMap(ctx context.Context, historyService types.ExchangeTradeHistoryService, pinOrdersOpen, pinOrdersFilled PinOrderMap, existedOrders *types.SyncOrderMap, since, until time.Time) error {
var fromTradeID uint64 = 0 var fromTradeID uint64 = 0
var limit int64 = 1000
for { for {
trades, err := historyService.QueryTrades(ctx, s.Symbol, &types.TradeQueryOptions{ trades, err := historyService.QueryTrades(ctx, s.Symbol, &types.TradeQueryOptions{
StartTime: &since,
EndTime: &until,
LastTradeID: fromTradeID, LastTradeID: fromTradeID,
Limit: limit, Limit: limit,
}) })
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to query trades to recover the grid with open orders") return errors.Wrapf(err, "failed to query trades to recover the grid with open orders")
} }
s.debugLog("QueryTrades return %d trades", len(trades)) s.debugLog("QueryTrades from %s <-> %s (from: %d) return %d trades", since, until, fromTradeID, len(trades))
for _, trade := range trades { for _, trade := range trades {
if trade.Time.After(until) {
return nil
}
s.debugLog(trade.String()) s.debugLog(trade.String())
if existedOrders.Exists(trade.OrderID) { if existedOrders.Exists(trade.OrderID) {
// already queries, skip // already queries, skip
continue continue
@ -240,7 +288,7 @@ func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, history
}) })
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to query order by trade") return errors.Wrapf(err, "failed to query order by trade")
} }
s.debugLog("%s (group_id: %d)", order.String(), order.GroupID) s.debugLog("%s (group_id: %d)", order.String(), order.GroupID)
@ -255,7 +303,7 @@ func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, history
pin := order.Price pin := order.Price
v, exist := pinOrdersOpen[pin] v, exist := pinOrdersOpen[pin]
if !exist { if !exist {
return nil, fmt.Errorf("the price of the order with the same GroupID is not in pins") return fmt.Errorf("the price of the order with the same GroupID is not in pins")
} }
// skip open orders on grid // skip open orders on grid
@ -275,11 +323,9 @@ func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, history
// stop condition // stop condition
if int64(len(trades)) < limit { if int64(len(trades)) < limit {
break return nil
} }
} }
return pinOrdersFilled, nil
} }
func addOrdersIntoPinOrderMap(pinOrders PinOrderMap, orders []types.Order) error { func addOrdersIntoPinOrderMap(pinOrders PinOrderMap, orders []types.Order) error {

View File

@ -162,8 +162,11 @@ type Strategy struct {
// it makes sure that your grid configuration is profitable. // it makes sure that your grid configuration is profitable.
FeeRate fixedpoint.Value `json:"feeRate"` FeeRate fixedpoint.Value `json:"feeRate"`
SkipSpreadCheck bool `json:"skipSpreadCheck"` SkipSpreadCheck bool `json:"skipSpreadCheck"`
RecoverGridByScanningTrades bool `json:"recoverGridByScanningTrades"`
// recover related config
RecoverGridByScanningTrades bool `json:"recoverGridByScanningTrades"`
RecoverWithin time.Duration `json:"recoverWithin"`
// Debug enables the debug mode // Debug enables the debug mode
Debug bool `json:"debug"` Debug bool `json:"debug"`