Merge pull request #1119 from c9s/feature/grids/recover-from-trades

FEATURE: make PinOrderMap's key from string to Pin
This commit is contained in:
kbearXD 2023-03-17 10:45:09 +08:00 committed by GitHub
commit 294c09b9e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 55 deletions

View File

@ -4,11 +4,12 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
) )
// PinOrderMap store the pin-order's relation, we will change key from string to fixedpoint.Value when FormatString fixed // PinOrderMap store the pin-order's relation, we will change key from string to fixedpoint.Value when FormatString fixed
type PinOrderMap map[string]types.Order type PinOrderMap map[fixedpoint.Value]types.Order
// AscendingOrders get the orders from pin order map and sort it in asc order // AscendingOrders get the orders from pin order map and sort it in asc order
func (m PinOrderMap) AscendingOrders() []types.Order { func (m PinOrderMap) AscendingOrders() []types.Order {
@ -41,7 +42,7 @@ func (m PinOrderMap) String() string {
sb.WriteString("================== PIN ORDER MAP ==================\n") sb.WriteString("================== PIN ORDER MAP ==================\n")
for pin, order := range m { for pin, order := range m {
sb.WriteString(fmt.Sprintf("%s -> %s\n", pin, order.String())) sb.WriteString(fmt.Sprintf("%+v -> %s\n", pin, order.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

@ -1465,11 +1465,11 @@ func (s *Strategy) verifyFilledGrid(pins []Pin, pinOrders PinOrderMap, filledOrd
s.debugOrders("filled orders", filledOrders) s.debugOrders("filled orders", filledOrders)
for _, filledOrder := range filledOrders { for _, filledOrder := range filledOrders {
price := s.Market.FormatPrice(filledOrder.Price) price := filledOrder.Price
if o, exist := pinOrders[price]; !exist { if o, exist := pinOrders[price]; !exist {
return fmt.Errorf("the price (%s) is not in pins", price) return fmt.Errorf("the price (%+v) is not in pins", price)
} else if o.OrderID != 0 { } else if o.OrderID != 0 {
return fmt.Errorf("there is already an order at this price (%s)", price) return fmt.Errorf("there is already an order at this price (%+v)", price)
} else { } else {
pinOrders[price] = filledOrder pinOrders[price] = filledOrder
} }
@ -1479,10 +1479,9 @@ func (s *Strategy) verifyFilledGrid(pins []Pin, pinOrders PinOrderMap, filledOrd
side := types.SideTypeBuy side := types.SideTypeBuy
for _, pin := range pins { for _, pin := range pins {
price := s.Market.FormatPrice(fixedpoint.Value(pin)) order, exist := pinOrders[fixedpoint.Value(pin)]
order, exist := pinOrders[price]
if !exist { if !exist {
return fmt.Errorf("there is no order at price (%s)", price) return fmt.Errorf("there is no order at price (%+v)", pin)
} }
// if there is order with OrderID = 0, means we hit the empty pin // if there is order with OrderID = 0, means we hit the empty pin
@ -1516,13 +1515,12 @@ func (s *Strategy) buildPinOrderMap(pins []Pin, openOrders []types.Order) (PinOr
pinOrderMap := make(PinOrderMap) pinOrderMap := make(PinOrderMap)
for _, pin := range pins { for _, pin := range pins {
priceStr := s.Market.FormatPrice(fixedpoint.Value(pin)) pinOrderMap[fixedpoint.Value(pin)] = types.Order{}
pinOrderMap[priceStr] = types.Order{}
} }
for _, openOrder := range openOrders { for _, openOrder := range openOrders {
priceStr := s.Market.FormatPrice(openOrder.Price) pin := openOrder.Price
v, exist := pinOrderMap[priceStr] v, exist := pinOrderMap[pin]
if !exist { if !exist {
return nil, fmt.Errorf("the price of the order (id: %d) is not in pins", openOrder.OrderID) return nil, fmt.Errorf("the price of the order (id: %d) is not in pins", openOrder.OrderID)
} }
@ -1531,7 +1529,7 @@ func (s *Strategy) buildPinOrderMap(pins []Pin, openOrders []types.Order) (PinOr
return nil, fmt.Errorf("there are duplicated open orders at the same pin") return nil, fmt.Errorf("there are duplicated open orders at the same pin")
} }
pinOrderMap[priceStr] = openOrder pinOrderMap[pin] = openOrder
} }
return pinOrderMap, nil return pinOrderMap, nil
@ -1590,8 +1588,8 @@ func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, history
} }
// checked the trade's order is filled order // checked the trade's order is filled order
priceStr := s.Market.FormatPrice(order.Price) pin := order.Price
v, exist := pinOrdersOpen[priceStr] 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 nil, fmt.Errorf("the price of the order with the same GroupID is not in pins")
} }
@ -1602,13 +1600,13 @@ func (s *Strategy) buildFilledPinOrderMapFromTrades(ctx context.Context, history
} }
// check the order's creation time // check the order's creation time
if pinOrder, exist := pinOrdersFilled[priceStr]; exist && pinOrder.CreationTime.Time().After(order.CreationTime.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 // 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. // 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) s.logger.Infof("pinOrder's creation time (%s) should not be after order's creation time (%s)", pinOrder.CreationTime, order.CreationTime)
continue continue
} }
pinOrdersFilled[priceStr] = *order pinOrdersFilled[pin] = *order
// wait 100 ms to avoid rate limit // wait 100 ms to avoid rate limit
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)

View File

@ -1100,7 +1100,7 @@ func Test_buildPinOrderMap(t *testing.T) {
assert.Len(m, 11) assert.Len(m, 11)
for pin, order := range m { for pin, order := range m {
if pin == s.FormatPrice(openOrders[0].Price) { if pin == openOrders[0].Price {
assert.Equal(openOrders[0].OrderID, order.OrderID) assert.Equal(openOrders[0].OrderID, order.OrderID)
} else { } else {
assert.Equal(uint64(0), order.OrderID) assert.Equal(uint64(0), order.OrderID)
@ -1184,19 +1184,19 @@ func Test_getOrdersFromPinOrderMapInAscOrder(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
now := time.Now() now := time.Now()
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"1000": types.Order{ number("1000"): types.Order{
OrderID: 1, OrderID: 1,
CreationTime: types.Time(now.Add(1 * time.Hour)), CreationTime: types.Time(now.Add(1 * time.Hour)),
UpdateTime: types.Time(now.Add(5 * time.Hour)), UpdateTime: types.Time(now.Add(5 * time.Hour)),
}, },
"1100": types.Order{}, number("1100"): types.Order{},
"1200": types.Order{}, number("1200"): types.Order{},
"1300": types.Order{ number("1300"): types.Order{
OrderID: 3, OrderID: 3,
CreationTime: types.Time(now.Add(3 * time.Hour)), CreationTime: types.Time(now.Add(3 * time.Hour)),
UpdateTime: types.Time(now.Add(6 * time.Hour)), UpdateTime: types.Time(now.Add(6 * time.Hour)),
}, },
"1400": types.Order{ number("1400"): types.Order{
OrderID: 2, OrderID: 2,
CreationTime: types.Time(now.Add(2 * time.Hour)), CreationTime: types.Time(now.Add(2 * time.Hour)),
UpdateTime: types.Time(now.Add(4 * time.Hour)), UpdateTime: types.Time(now.Add(4 * time.Hour)),
@ -1220,20 +1220,20 @@ func Test_verifyFilledGrid(t *testing.T) {
t.Run("valid grid with buy/sell orders", func(t *testing.T) { t.Run("valid grid with buy/sell orders", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{ number("100.00"): types.Order{
OrderID: 1, OrderID: 1,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"200.00": types.Order{}, number("200.00"): types.Order{},
"300.00": types.Order{ number("300.00"): types.Order{
OrderID: 3, OrderID: 3,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"400.00": types.Order{ number("400.00"): types.Order{
OrderID: 4, OrderID: 4,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
@ -1245,45 +1245,45 @@ func Test_verifyFilledGrid(t *testing.T) {
}) })
t.Run("valid grid with only buy orders", func(t *testing.T) { t.Run("valid grid with only buy orders", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{ number("100.00"): types.Order{
OrderID: 1, OrderID: 1,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"200.00": types.Order{ number("200.00"): types.Order{
OrderID: 2, OrderID: 2,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"300.00": types.Order{ number("300.00"): types.Order{
OrderID: 3, OrderID: 3,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"400.00": types.Order{}, number("400.00"): types.Order{},
} }
assert.NoError(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil)) assert.NoError(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
}) })
t.Run("valid grid with only sell orders", func(t *testing.T) { t.Run("valid grid with only sell orders", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{}, number("100.00"): types.Order{},
"200.00": types.Order{ number("200.00"): types.Order{
OrderID: 2, OrderID: 2,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"300.00": types.Order{ number("300.00"): types.Order{
OrderID: 3, OrderID: 3,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"400.00": types.Order{ number("400.00"): types.Order{
OrderID: 4, OrderID: 4,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
@ -1295,15 +1295,15 @@ func Test_verifyFilledGrid(t *testing.T) {
}) })
t.Run("invalid grid with multiple empty pins", func(t *testing.T) { t.Run("invalid grid with multiple empty pins", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{ number("100.00"): types.Order{
OrderID: 1, OrderID: 1,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"200.00": types.Order{}, number("200.00"): types.Order{},
"300.00": types.Order{}, number("300.00"): types.Order{},
"400.00": types.Order{ number("400.00"): types.Order{
OrderID: 4, OrderID: 4,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
@ -1315,25 +1315,25 @@ func Test_verifyFilledGrid(t *testing.T) {
}) })
t.Run("invalid grid without empty pin", func(t *testing.T) { t.Run("invalid grid without empty pin", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{ number("100.00"): types.Order{
OrderID: 1, OrderID: 1,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"200.00": types.Order{ number("200.00"): types.Order{
OrderID: 2, OrderID: 2,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"300.00": types.Order{ number("300.00"): types.Order{
OrderID: 3, OrderID: 3,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"400.00": types.Order{ number("400.00"): types.Order{
OrderID: 4, OrderID: 4,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
@ -1345,20 +1345,20 @@ func Test_verifyFilledGrid(t *testing.T) {
}) })
t.Run("invalid grid with Buy-empty-Sell-Buy order", func(t *testing.T) { t.Run("invalid grid with Buy-empty-Sell-Buy order", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{ number("100.00"): types.Order{
OrderID: 1, OrderID: 1,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"200.00": types.Order{}, number("200.00"): types.Order{},
"300.00": types.Order{ number("300.00"): types.Order{
OrderID: 3, OrderID: 3,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"400.00": types.Order{ number("400.00"): types.Order{
OrderID: 4, OrderID: 4,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
@ -1370,45 +1370,45 @@ func Test_verifyFilledGrid(t *testing.T) {
}) })
t.Run("invalid grid with Sell-empty order", func(t *testing.T) { t.Run("invalid grid with Sell-empty order", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{ number("100.00"): types.Order{
OrderID: 1, OrderID: 1,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"200.00": types.Order{ number("200.00"): types.Order{
OrderID: 2, OrderID: 2,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"300.00": types.Order{ number("300.00"): types.Order{
OrderID: 3, OrderID: 3,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeSell, Side: types.SideTypeSell,
}, },
}, },
"400.00": types.Order{}, number("400.00"): types.Order{},
} }
assert.Error(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil)) assert.Error(s.verifyFilledGrid(s.grid.Pins, pinOrderMap, nil))
}) })
t.Run("invalid grid with empty-Buy order", func(t *testing.T) { t.Run("invalid grid with empty-Buy order", func(t *testing.T) {
pinOrderMap := PinOrderMap{ pinOrderMap := PinOrderMap{
"100.00": types.Order{}, number("100.00"): types.Order{},
"200.00": types.Order{ number("200.00"): types.Order{
OrderID: 2, OrderID: 2,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"300.00": types.Order{ number("300.00"): types.Order{
OrderID: 3, OrderID: 3,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
}, },
}, },
"400.00": types.Order{ number("400.00"): types.Order{
OrderID: 4, OrderID: 4,
SubmitOrder: types.SubmitOrder{ SubmitOrder: types.SubmitOrder{
Side: types.SideTypeBuy, Side: types.SideTypeBuy,