Skip to content

Commit 3717569

Browse files
committed
FEATURE: recover the grid when there is error at grid opening
1 parent 5c33c76 commit 3717569

File tree

2 files changed

+160
-3
lines changed

2 files changed

+160
-3
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package grid2
2+
3+
import (
4+
"context"
5+
"strings"
6+
7+
"github.com/c9s/bbgo/pkg/fixedpoint"
8+
"github.com/c9s/bbgo/pkg/types"
9+
"go.uber.org/multierr"
10+
)
11+
12+
type GridOrder struct {
13+
OrderID uint64 `json:"orderID"`
14+
ClientOrderID string `json:"clientOrderID"`
15+
Side types.SideType `json:"side"`
16+
Price fixedpoint.Value `json:"price"`
17+
Quantity fixedpoint.Value `json:"quantity"`
18+
}
19+
20+
type GridOrderStates struct {
21+
Orders map[fixedpoint.Value]GridOrder `json:"orders"`
22+
}
23+
24+
func newGridOrderStates() *GridOrderStates {
25+
return &GridOrderStates{
26+
Orders: make(map[fixedpoint.Value]GridOrder),
27+
}
28+
}
29+
30+
func (s *GridOrderStates) AddCreatedOrders(createdOrders ...types.Order) {
31+
for _, createdOrder := range createdOrders {
32+
s.Orders[createdOrder.Price] = GridOrder{
33+
OrderID: createdOrder.OrderID,
34+
ClientOrderID: createdOrder.ClientOrderID,
35+
Side: createdOrder.Side,
36+
Price: createdOrder.Price,
37+
Quantity: createdOrder.Quantity,
38+
}
39+
}
40+
}
41+
42+
func (s *GridOrderStates) AddSubmitOrders(submitOrders ...types.SubmitOrder) {
43+
for _, submitOrder := range submitOrders {
44+
s.Orders[submitOrder.Price] = GridOrder{
45+
ClientOrderID: submitOrder.ClientOrderID,
46+
Side: submitOrder.Side,
47+
Price: submitOrder.Price,
48+
Quantity: submitOrder.Quantity,
49+
}
50+
}
51+
}
52+
53+
func (s *GridOrderStates) GetFailedOrdersWhenGridOpening(ctx context.Context, orderQueryService types.ExchangeOrderQueryService) ([]GridOrder, error) {
54+
var failed []GridOrder
55+
var errs error
56+
57+
for _, order := range s.Orders {
58+
if order.OrderID == 0 {
59+
_, err := orderQueryService.QueryOrder(ctx, types.OrderQuery{
60+
ClientOrderID: order.ClientOrderID,
61+
})
62+
63+
if err != nil {
64+
// error handle
65+
if strings.Contains(err.Error(), "resource not found") {
66+
// not found error, need to re-place this order
67+
// if order not found: {"success":false,"error":{"code":404,"message":"resource not found"}}
68+
failed = append(failed, order)
69+
} else {
70+
// other error
71+
// need to log the error and stop
72+
errs = multierr.Append(errs, err)
73+
}
74+
75+
continue
76+
}
77+
}
78+
}
79+
80+
return failed, errs
81+
}

pkg/strategy/grid2/strategy.go

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,19 @@ type Strategy struct {
162162
// it makes sure that your grid configuration is profitable.
163163
FeeRate fixedpoint.Value `json:"feeRate"`
164164

165-
SkipSpreadCheck bool `json:"skipSpreadCheck"`
166-
RecoverGridByScanningTrades bool `json:"recoverGridByScanningTrades"`
165+
SkipSpreadCheck bool `json:"skipSpreadCheck"`
166+
RecoverGridByScanningTrades bool `json:"recoverGridByScanningTrades"`
167+
RecoverFailedOrdersWhenGridOpening bool `json:"recoverFailedOrdersWhenGridOpening"`
167168

168169
// Debug enables the debug mode
169170
Debug bool `json:"debug"`
170171

171172
GridProfitStats *GridProfitStats `persistence:"grid_profit_stats"`
172173
Position *types.Position `persistence:"position"`
173174

175+
// this is used to check all generated orders are placed
176+
GridOrderStates *GridOrderStates `persistence:"grid_order_states"`
177+
174178
// ExchangeSession is an injection field
175179
ExchangeSession *bbgo.ExchangeSession
176180

@@ -1068,6 +1072,10 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
10681072
return err
10691073
}
10701074

1075+
// use grid order states to check the orders are placed when opening grid
1076+
s.GridOrderStates = newGridOrderStates()
1077+
s.GridOrderStates.AddSubmitOrders(submitOrders...)
1078+
10711079
s.debugGridOrders(submitOrders, lastPrice)
10721080

10731081
writeCtx := s.getWriteContext(ctx)
@@ -1078,6 +1086,8 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
10781086
return err2
10791087
}
10801088

1089+
s.GridOrderStates.AddCreatedOrders(createdOrders...)
1090+
10811091
// try to always emit grid ready
10821092
defer s.EmitGridReady()
10831093

@@ -1230,14 +1240,33 @@ func (s *Strategy) debugOrders(desc string, orders []types.Order) {
12301240
s.logger.Infof(sb.String())
12311241
}
12321242

1243+
func (s *Strategy) debugSubmitOrders(desc string, orders []types.SubmitOrder) {
1244+
if !s.Debug {
1245+
return
1246+
}
1247+
1248+
var sb strings.Builder
1249+
1250+
if desc == "" {
1251+
desc = "ORDERS"
1252+
}
1253+
1254+
sb.WriteString(desc + " [\n")
1255+
for i, order := range orders {
1256+
sb.WriteString(fmt.Sprintf(" - %d) %s\n", i, order.String()))
1257+
}
1258+
sb.WriteString("]")
1259+
1260+
s.logger.Infof(sb.String())
1261+
}
1262+
12331263
func (s *Strategy) debugGridProfitStats(trigger string) {
12341264
if !s.Debug {
12351265
return
12361266
}
12371267

12381268
stats := *s.GridProfitStats
12391269
// ProfitEntries may have too many profits, make it nil to readable
1240-
stats.ProfitEntries = nil
12411270
b, err := json.Marshal(stats)
12421271
if err != nil {
12431272
s.logger.WithError(err).Errorf("[%s] failed to debug grid profit stats", trigger)
@@ -1936,6 +1965,15 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
19361965

19371966
func (s *Strategy) startProcess(ctx context.Context, session *bbgo.ExchangeSession) {
19381967
s.debugGridProfitStats("startProcess")
1968+
if s.RecoverFailedOrdersWhenGridOpening {
1969+
s.logger.Info("recover failed orders when grid opening")
1970+
if err := s.recoverFailedOrdersWhenGridOpening(ctx); err != nil {
1971+
s.logger.WithError(err).Error("failed to start process, recover failed orders when grid opening error")
1972+
s.EmitGridError(errors.Wrapf(err, "failed to start process, recover failed orders when grid opening error"))
1973+
return
1974+
}
1975+
}
1976+
19391977
if s.RecoverOrdersWhenStart {
19401978
// do recover only when triggerPrice is not set and not in the back-test mode
19411979
s.logger.Infof("recoverWhenStart is set, trying to recover grid orders...")
@@ -1954,6 +1992,44 @@ func (s *Strategy) startProcess(ctx context.Context, session *bbgo.ExchangeSessi
19541992
}
19551993
}
19561994

1995+
func (s *Strategy) recoverFailedOrdersWhenGridOpening(ctx context.Context) error {
1996+
if s.GridOrderStates == nil {
1997+
return nil
1998+
}
1999+
2000+
failedOrders, err := s.GridOrderStates.GetFailedOrdersWhenGridOpening(ctx, s.orderQueryService)
2001+
if err != nil {
2002+
return errors.Wrapf(err, "failed to get failed orders")
2003+
}
2004+
2005+
var submitOrders []types.SubmitOrder
2006+
for _, failedOrder := range failedOrders {
2007+
submitOrders = append(submitOrders, types.SubmitOrder{
2008+
Symbol: s.Symbol,
2009+
Type: types.OrderTypeLimit,
2010+
Side: failedOrder.Side,
2011+
Price: failedOrder.Price,
2012+
Quantity: failedOrder.Quantity,
2013+
Market: s.Market,
2014+
TimeInForce: types.TimeInForceGTC,
2015+
Tag: orderTag,
2016+
GroupID: s.OrderGroupID,
2017+
ClientOrderID: failedOrder.ClientOrderID,
2018+
})
2019+
}
2020+
2021+
s.debugSubmitOrders("RECOVER FAILED ORDERS WHEN GRID OPENING", submitOrders)
2022+
2023+
writeCtx := s.getWriteContext(ctx)
2024+
createdOrders, err := s.orderExecutor.SubmitOrders(writeCtx, submitOrders...)
2025+
if err != nil {
2026+
return errors.Wrapf(err, "failed to submit orders")
2027+
}
2028+
2029+
s.GridOrderStates.AddCreatedOrders(createdOrders...)
2030+
return nil
2031+
}
2032+
19572033
func (s *Strategy) recoverGrid(ctx context.Context, session *bbgo.ExchangeSession) error {
19582034
if s.RecoverGridByScanningTrades {
19592035
s.debugLog("recovering grid by scanning trades")

0 commit comments

Comments
 (0)