mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
REFACTOR: refactor for future test
This commit is contained in:
parent
61892eb2df
commit
f38cfb6ea3
|
@ -13,6 +13,14 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
type QueryTradesService interface {
|
||||
QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) ([]types.Trade, error)
|
||||
}
|
||||
|
||||
type QueryOrderService interface {
|
||||
QueryOrder(ctx context.Context, q types.OrderQuery) (*types.Order, error)
|
||||
}
|
||||
|
||||
func (s *Strategy) recoverByScanningTrades(ctx context.Context, session *bbgo.ExchangeSession) error {
|
||||
defer func() {
|
||||
s.updateGridNumOfOrdersMetricsWithLock()
|
||||
|
@ -59,9 +67,20 @@ func (s *Strategy) recoverByScanningTrades(ctx context.Context, session *bbgo.Ex
|
|||
}
|
||||
|
||||
s.logger.Infof("start recovering")
|
||||
if err := s.recoverWithOpenOrdersByScanningTrades(ctx, historyService, openOrders); err != nil {
|
||||
filledOrders, err := s.getFilledOrdersByScanningTrades(ctx, historyService, s.orderQueryService, openOrders)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "grid recover error")
|
||||
}
|
||||
s.debugOrders("emit filled orders", filledOrders)
|
||||
|
||||
// add open orders into avtive maker orders
|
||||
s.addOrdersToActiveOrderBook(openOrders)
|
||||
|
||||
// emit the filled orders
|
||||
activeOrderBook := s.orderExecutor.ActiveMakerOrders()
|
||||
for _, filledOrder := range filledOrders {
|
||||
activeOrderBook.EmitFilled(filledOrder)
|
||||
}
|
||||
|
||||
// emit ready after recover
|
||||
s.EmitGridReady()
|
||||
|
@ -96,11 +115,7 @@ func (s *Strategy) recoverByScanningTrades(ctx context.Context, session *bbgo.Ex
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) recoverWithOpenOrdersByScanningTrades(ctx context.Context, historyService types.ExchangeTradeHistoryService, openOrdersOnGrid []types.Order) error {
|
||||
if s.orderQueryService == nil {
|
||||
return fmt.Errorf("orderQueryService is nil, it can't get orders by trade")
|
||||
}
|
||||
|
||||
func (s *Strategy) getFilledOrdersByScanningTrades(ctx context.Context, queryTradesService QueryTradesService, queryOrderService QueryOrderService, openOrdersOnGrid []types.Order) ([]types.Order, error) {
|
||||
// set grid
|
||||
grid := s.newGrid()
|
||||
s.setGrid(grid)
|
||||
|
@ -110,22 +125,21 @@ func (s *Strategy) recoverWithOpenOrdersByScanningTrades(ctx context.Context, hi
|
|||
s.debugLog("open orders nums: %d, expected nums: %d", numGridOpenOrders, expectedNumOfOrders)
|
||||
if expectedNumOfOrders == numGridOpenOrders {
|
||||
// no need to recover, only need to add open orders back to active order book
|
||||
s.addOrdersToActiveOrderBook(openOrdersOnGrid)
|
||||
return nil
|
||||
return nil, nil
|
||||
} else if expectedNumOfOrders < numGridOpenOrders {
|
||||
return fmt.Errorf("amount of grid's open orders should not > amount of expected grid's orders")
|
||||
return nil, fmt.Errorf("amount of grid's open orders should not > amount of expected grid's orders")
|
||||
}
|
||||
|
||||
// 1. build twin-order map
|
||||
twinOrdersOpen, err := s.buildTwinOrderMap(grid.Pins, openOrdersOnGrid)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to build pin order map with open orders")
|
||||
return nil, errors.Wrapf(err, "failed to build pin order map with open orders")
|
||||
}
|
||||
|
||||
// 2. build the filled twin-order map by querying trades
|
||||
twinOrdersFilled, err := s.buildFilledTwinOrderMapFromTrades(ctx, historyService, twinOrdersOpen)
|
||||
twinOrdersFilled, err := s.buildFilledTwinOrderMapFromTrades(ctx, queryTradesService, queryOrderService, twinOrdersOpen)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to build filled pin order map")
|
||||
return nil, errors.Wrapf(err, "failed to build filled pin order map")
|
||||
}
|
||||
|
||||
// 3. get the filled orders from twin-order map
|
||||
|
@ -133,182 +147,10 @@ func (s *Strategy) recoverWithOpenOrdersByScanningTrades(ctx context.Context, hi
|
|||
|
||||
// 4. verify the grid
|
||||
if err := s.verifyFilledTwinGrid(s.grid.Pins, twinOrdersOpen, filledOrders); err != nil {
|
||||
return errors.Wrapf(err, "verify grid with error")
|
||||
return nil, errors.Wrapf(err, "verify grid with error")
|
||||
}
|
||||
|
||||
// 5. add open orders to active order book.
|
||||
s.addOrdersToActiveOrderBook(openOrdersOnGrid)
|
||||
|
||||
// 6. emit the filled orders
|
||||
s.debugOrders("emit filled orders", filledOrders)
|
||||
activeOrderBook := s.orderExecutor.ActiveMakerOrders()
|
||||
for _, filledOrder := range filledOrders {
|
||||
activeOrderBook.EmitFilled(filledOrder)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) verifyFilledGrid(pins []Pin, pinOrders PinOrderMap, filledOrders []types.Order) error {
|
||||
s.debugLog("verifying filled grid - pins: %+v", pins)
|
||||
s.debugLog("verifying filled grid - open pin orders:\n%s", pinOrders.String())
|
||||
s.debugOrders("verifying filled grid - filled orders", filledOrders)
|
||||
|
||||
if err := addOrdersIntoPinOrderMap(pinOrders, filledOrders); err != nil {
|
||||
return errors.Wrapf(err, "verifying filled grid error when add orders into pin order map")
|
||||
}
|
||||
|
||||
s.debugLog("verifying filled grid - filled pin orders:\n%+v", pinOrders.String())
|
||||
|
||||
expectedSide := types.SideTypeBuy
|
||||
for _, pin := range pins {
|
||||
order, exist := pinOrders[fixedpoint.Value(pin)]
|
||||
if !exist {
|
||||
return fmt.Errorf("there is no order at price (%+v)", pin)
|
||||
}
|
||||
|
||||
// if there is order with OrderID = 0, means we hit the empty pin
|
||||
// there must be only one empty pin in the grid
|
||||
// all orders below this pin need to be bid orders, above this pin need to be ask orders
|
||||
if order.OrderID == 0 {
|
||||
if expectedSide == types.SideTypeBuy {
|
||||
expectedSide = types.SideTypeSell
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("found more than one empty pins")
|
||||
}
|
||||
|
||||
if order.Side != expectedSide {
|
||||
return fmt.Errorf("the side of order (%s) is wrong, expected: %s", order.Side, expectedSide)
|
||||
}
|
||||
}
|
||||
|
||||
if expectedSide != types.SideTypeSell {
|
||||
return fmt.Errorf("there is no empty pin in the grid")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildPinOrderMap build the pin-order map with grid and open orders.
|
||||
// The keys of this map contains all required pins of this grid.
|
||||
// If the Order of the pin is empty types.Order (OrderID == 0), it means there is no open orders at this pin.
|
||||
func (s *Strategy) buildPinOrderMap(pins []Pin, openOrders []types.Order) (PinOrderMap, error) {
|
||||
pinOrderMap := make(PinOrderMap)
|
||||
|
||||
for _, pin := range pins {
|
||||
pinOrderMap[fixedpoint.Value(pin)] = types.Order{}
|
||||
}
|
||||
|
||||
for _, openOrder := range openOrders {
|
||||
pin := openOrder.Price
|
||||
v, exist := pinOrderMap[pin]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("the price of the order (id: %d) is not in pins", openOrder.OrderID)
|
||||
}
|
||||
|
||||
if v.OrderID != 0 {
|
||||
return nil, fmt.Errorf("there are duplicated open orders at the same pin")
|
||||
}
|
||||
|
||||
pinOrderMap[pin] = openOrder
|
||||
}
|
||||
|
||||
return pinOrderMap, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, historyService types.ExchangeTradeHistoryService, pinOrdersOpen PinOrderMap) (PinOrderMap, error) {
|
||||
pinOrdersFilled := make(PinOrderMap)
|
||||
|
||||
// existedOrders is used to avoid re-query the same orders
|
||||
existedOrders := pinOrdersOpen.SyncOrderMap()
|
||||
|
||||
var limit int64 = 1000
|
||||
// get the filled orders when bbgo is down in order from trades
|
||||
// [NOTE] only retrieve from last 24 hours !!!
|
||||
var fromTradeID uint64 = 0
|
||||
for {
|
||||
trades, err := historyService.QueryTrades(ctx, s.Symbol, &types.TradeQueryOptions{
|
||||
LastTradeID: fromTradeID,
|
||||
Limit: limit,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to query trades to recover the grid with open orders")
|
||||
}
|
||||
|
||||
s.debugLog("QueryTrades return %d trades", len(trades))
|
||||
|
||||
for _, trade := range trades {
|
||||
s.debugLog(trade.String())
|
||||
if existedOrders.Exists(trade.OrderID) {
|
||||
// already queries, skip
|
||||
continue
|
||||
}
|
||||
|
||||
order, err := s.orderQueryService.QueryOrder(ctx, types.OrderQuery{
|
||||
OrderID: strconv.FormatUint(trade.OrderID, 10),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to query order by trade")
|
||||
}
|
||||
|
||||
s.debugLog("%s (group_id: %d)", order.String(), order.GroupID)
|
||||
|
||||
// avoid query this order again
|
||||
existedOrders.Add(*order)
|
||||
|
||||
// add 1 to avoid duplicate
|
||||
fromTradeID = trade.ID + 1
|
||||
|
||||
// checked the trade's order is filled order
|
||||
pin := order.Price
|
||||
v, exist := pinOrdersOpen[pin]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("the price of the order with the same GroupID is not in pins")
|
||||
}
|
||||
|
||||
// skip open orders on grid
|
||||
if v.OrderID != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// check the order's creation time
|
||||
if pinOrder, exist := pinOrdersFilled[pin]; exist && pinOrder.CreationTime.Time().After(order.CreationTime.Time()) {
|
||||
// do not replace the pin order if the order's creation time is not after pin order's creation time
|
||||
// this situation should not happen actually, because the trades is already sorted.
|
||||
s.logger.Infof("pinOrder's creation time (%s) should not be after order's creation time (%s)", pinOrder.CreationTime, order.CreationTime)
|
||||
continue
|
||||
}
|
||||
pinOrdersFilled[pin] = *order
|
||||
}
|
||||
|
||||
// stop condition
|
||||
if int64(len(trades)) < limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return pinOrdersFilled, nil
|
||||
}
|
||||
|
||||
func addOrdersIntoPinOrderMap(pinOrders PinOrderMap, orders []types.Order) error {
|
||||
for _, order := range orders {
|
||||
price := order.Price
|
||||
if o, exist := pinOrders[price]; !exist {
|
||||
return fmt.Errorf("the price (%+v) is not in pins", price)
|
||||
} else if o.OrderID != 0 {
|
||||
return fmt.Errorf("there is already an order at this price (%+v)", price)
|
||||
} else {
|
||||
pinOrders[price] = order
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return filledOrders, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) verifyFilledTwinGrid(pins []Pin, twinOrders TwinOrderMap, filledOrders []types.Order) error {
|
||||
|
@ -384,7 +226,7 @@ func (s *Strategy) buildTwinOrderMap(pins []Pin, openOrders []types.Order) (Twin
|
|||
|
||||
// buildFilledTwinOrderMapFromTrades 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
|
||||
func (s *Strategy) buildFilledTwinOrderMapFromTrades(ctx context.Context, historyService types.ExchangeTradeHistoryService, twinOrdersOpen TwinOrderMap) (TwinOrderMap, error) {
|
||||
func (s *Strategy) buildFilledTwinOrderMapFromTrades(ctx context.Context, queryTradesService QueryTradesService, queryOrderService QueryOrderService, twinOrdersOpen TwinOrderMap) (TwinOrderMap, error) {
|
||||
twinOrdersFilled := make(TwinOrderMap)
|
||||
|
||||
// existedOrders is used to avoid re-query the same orders
|
||||
|
@ -395,7 +237,7 @@ func (s *Strategy) buildFilledTwinOrderMapFromTrades(ctx context.Context, histor
|
|||
// [NOTE] only retrieve from last 24 hours !!!
|
||||
var fromTradeID uint64 = 0
|
||||
for {
|
||||
trades, err := historyService.QueryTrades(ctx, s.Symbol, &types.TradeQueryOptions{
|
||||
trades, err := queryTradesService.QueryTrades(ctx, s.Symbol, &types.TradeQueryOptions{
|
||||
LastTradeID: fromTradeID,
|
||||
Limit: limit,
|
||||
})
|
||||
|
@ -413,7 +255,7 @@ func (s *Strategy) buildFilledTwinOrderMapFromTrades(ctx context.Context, histor
|
|||
continue
|
||||
}
|
||||
|
||||
order, err := s.orderQueryService.QueryOrder(ctx, types.OrderQuery{
|
||||
order, err := queryOrderService.QueryOrder(ctx, types.OrderQuery{
|
||||
OrderID: strconv.FormatUint(trade.OrderID, 10),
|
||||
})
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -1230,6 +1229,7 @@ func TestStrategy_checkMinimalQuoteInvestment(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
/*
|
||||
func Test_buildPinOrderMap(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
s := newTestStrategy()
|
||||
|
@ -1585,3 +1585,4 @@ func Test_verifyFilledGrid(t *testing.T) {
|
|||
})
|
||||
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user