Skip to content

Commit 8bcfb78

Browse files
authored
Merge pull request #1030 from c9s/feature/grid2
WIP: Feature/grid2
2 parents ed1c5c8 + bf87d04 commit 8bcfb78

File tree

8 files changed

+118
-77
lines changed

8 files changed

+118
-77
lines changed

apps/backtest-report/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Create a symlink to your back-test report output directory:
1919
Generate some back-test reports:
2020

2121
```
22-
(cd ../.. && go run ./cmd/bbgo backtest --config bollmaker_ethusdt.yaml --debug --session binance --output output --subdir)
22+
(cd ../.. && go run ./cmd/bbgo backtest --config bollmaker_ethusdt.yaml --debug --output output --subdir)
2323
```
2424

2525
Start the development server:

doc/topics/back-testing.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ godotenv -f .env.local -- go run ./cmd/bbgo backtest --config config/grid.yaml -
7676

7777
## See Also
7878

79-
If you want to test the max draw down (MDD) you can adjust the start date to somewhere near 2020-03-12
80-
81-
See <https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp>
79+
* [apps/backtest-report](../../apps/backtest-report) - BBGO's built-in backtest report viewer
8280

81+
* [MDD](https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp) - If you want to test the max draw down (MDD) you can adjust the start date to somewhere near 2020-03-12.

pkg/bbgo/activeorderbook.go

+4
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,7 @@ func (b *ActiveOrderBook) NumOfOrders() int {
295295
func (b *ActiveOrderBook) Orders() types.OrderSlice {
296296
return b.orders.Orders()
297297
}
298+
299+
func (b *ActiveOrderBook) Lookup(f func(o types.Order) bool) *types.Order {
300+
return b.orders.Lookup(f)
301+
}

pkg/exchange/binance/exchange.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ func init() {
5656
_ = types.FuturesExchange(&Exchange{})
5757

5858
if n, ok := util.GetEnvVarInt("BINANCE_ORDER_RATE_LIMITER"); ok {
59-
orderLimiter = rate.NewLimiter(rate.Limit(n), 2)
59+
orderLimiter = rate.NewLimiter(rate.Every(time.Duration(n)*time.Minute), 2)
6060
}
6161

6262
if n, ok := util.GetEnvVarInt("BINANCE_QUERY_TRADES_RATE_LIMITER"); ok {
63-
queryTradeLimiter = rate.NewLimiter(rate.Limit(n), 2)
63+
queryTradeLimiter = rate.NewLimiter(rate.Every(time.Duration(n)*time.Minute), 2)
6464
}
6565
}
6666

pkg/exchange/max/exchange.go

-61
Original file line numberDiff line numberDiff line change
@@ -384,67 +384,6 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err
384384
return err2
385385
}
386386

387-
func toMaxSubmitOrder(o types.SubmitOrder) (*maxapi.SubmitOrder, error) {
388-
symbol := toLocalSymbol(o.Symbol)
389-
orderType, err := toLocalOrderType(o.Type)
390-
if err != nil {
391-
return nil, err
392-
}
393-
394-
// case IOC type
395-
if orderType == maxapi.OrderTypeLimit && o.TimeInForce == types.TimeInForceIOC {
396-
orderType = maxapi.OrderTypeIOCLimit
397-
}
398-
399-
var quantityString string
400-
if o.Market.Symbol != "" {
401-
quantityString = o.Market.FormatQuantity(o.Quantity)
402-
} else {
403-
quantityString = o.Quantity.String()
404-
}
405-
406-
maxOrder := maxapi.SubmitOrder{
407-
Market: symbol,
408-
Side: toLocalSideType(o.Side),
409-
OrderType: orderType,
410-
Volume: quantityString,
411-
}
412-
413-
if o.GroupID > 0 {
414-
maxOrder.GroupID = o.GroupID
415-
}
416-
417-
clientOrderID := NewClientOrderID(o.ClientOrderID)
418-
if len(clientOrderID) > 0 {
419-
maxOrder.ClientOID = clientOrderID
420-
}
421-
422-
switch o.Type {
423-
case types.OrderTypeStopLimit, types.OrderTypeLimit, types.OrderTypeLimitMaker:
424-
var priceInString string
425-
if o.Market.Symbol != "" {
426-
priceInString = o.Market.FormatPrice(o.Price)
427-
} else {
428-
priceInString = o.Price.String()
429-
}
430-
maxOrder.Price = priceInString
431-
}
432-
433-
// set stop price field for limit orders
434-
switch o.Type {
435-
case types.OrderTypeStopLimit, types.OrderTypeStopMarket:
436-
var priceInString string
437-
if o.Market.Symbol != "" {
438-
priceInString = o.Market.FormatPrice(o.StopPrice)
439-
} else {
440-
priceInString = o.StopPrice.String()
441-
}
442-
maxOrder.StopPrice = priceInString
443-
}
444-
445-
return &maxOrder, nil
446-
}
447-
448387
func (e *Exchange) Withdraw(ctx context.Context, asset string, amount fixedpoint.Value, address string, options *types.WithdrawalOptions) error {
449388
asset = toLocalCurrency(asset)
450389

pkg/service/margin.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (s *MarginService) Sync(ctx context.Context, ex types.Exchange, asset strin
3434

3535
tasks := []SyncTask{
3636
{
37-
Select: SelectLastMarginLoans(ex.Name(), 100),
37+
Select: SelectLastMarginLoans(ex.Name(), asset, 100),
3838
Type: types.MarginLoan{},
3939
BatchQuery: func(ctx context.Context, startTime, endTime time.Time) (interface{}, chan error) {
4040
query := &batch.MarginLoanBatchQuery{
@@ -51,7 +51,7 @@ func (s *MarginService) Sync(ctx context.Context, ex types.Exchange, asset strin
5151
LogInsert: true,
5252
},
5353
{
54-
Select: SelectLastMarginRepays(ex.Name(), 100),
54+
Select: SelectLastMarginRepays(ex.Name(), asset, 100),
5555
Type: types.MarginRepay{},
5656
BatchQuery: func(ctx context.Context, startTime, endTime time.Time) (interface{}, chan error) {
5757
query := &batch.MarginRepayBatchQuery{
@@ -68,7 +68,7 @@ func (s *MarginService) Sync(ctx context.Context, ex types.Exchange, asset strin
6868
LogInsert: true,
6969
},
7070
{
71-
Select: SelectLastMarginInterests(ex.Name(), 100),
71+
Select: SelectLastMarginInterests(ex.Name(), asset, 100),
7272
Type: types.MarginInterest{},
7373
BatchQuery: func(ctx context.Context, startTime, endTime time.Time) (interface{}, chan error) {
7474
query := &batch.MarginInterestBatchQuery{
@@ -114,26 +114,26 @@ func (s *MarginService) Sync(ctx context.Context, ex types.Exchange, asset strin
114114
return nil
115115
}
116116

117-
func SelectLastMarginLoans(ex types.ExchangeName, limit uint64) sq.SelectBuilder {
117+
func SelectLastMarginLoans(ex types.ExchangeName, asset string, limit uint64) sq.SelectBuilder {
118118
return sq.Select("*").
119119
From("margin_loans").
120-
Where(sq.Eq{"exchange": ex}).
120+
Where(sq.Eq{"exchange": ex, "asset": asset}).
121121
OrderBy("time DESC").
122122
Limit(limit)
123123
}
124124

125-
func SelectLastMarginRepays(ex types.ExchangeName, limit uint64) sq.SelectBuilder {
125+
func SelectLastMarginRepays(ex types.ExchangeName, asset string, limit uint64) sq.SelectBuilder {
126126
return sq.Select("*").
127127
From("margin_repays").
128-
Where(sq.Eq{"exchange": ex}).
128+
Where(sq.Eq{"exchange": ex, "asset": asset}).
129129
OrderBy("time DESC").
130130
Limit(limit)
131131
}
132132

133-
func SelectLastMarginInterests(ex types.ExchangeName, limit uint64) sq.SelectBuilder {
133+
func SelectLastMarginInterests(ex types.ExchangeName, asset string, limit uint64) sq.SelectBuilder {
134134
return sq.Select("*").
135135
From("margin_interests").
136-
Where(sq.Eq{"exchange": ex}).
136+
Where(sq.Eq{"exchange": ex, "asset": asset}).
137137
OrderBy("time DESC").
138138
Limit(limit)
139139
}

pkg/strategy/grid2/strategy.go

+81
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strconv"
77
"sync"
8+
"time"
89

910
"github.com/pkg/errors"
1011
"github.com/sirupsen/logrus"
@@ -986,6 +987,86 @@ func (s *Strategy) checkMinimalQuoteInvestment() error {
986987
return nil
987988
}
988989

990+
func (s *Strategy) recoverGrid(ctx context.Context, session *bbgo.ExchangeSession) error {
991+
historyService, implemented := session.Exchange.(types.ExchangeTradeHistoryService)
992+
if !implemented {
993+
return nil
994+
}
995+
996+
openOrders, err := session.Exchange.QueryOpenOrders(ctx, s.Symbol)
997+
if err != nil {
998+
return err
999+
}
1000+
1001+
// no open orders, the grid is not placed yet
1002+
if len(openOrders) == 0 {
1003+
return nil
1004+
}
1005+
1006+
lastOrderID := uint64(0)
1007+
firstOrderTime := openOrders[0].CreationTime.Time()
1008+
lastOrderTime := firstOrderTime
1009+
for _, o := range openOrders {
1010+
if o.OrderID > lastOrderID {
1011+
lastOrderID = o.OrderID
1012+
}
1013+
1014+
createTime := o.CreationTime.Time()
1015+
if createTime.Before(firstOrderTime) {
1016+
firstOrderTime = createTime
1017+
} else if createTime.After(lastOrderTime) {
1018+
lastOrderTime = createTime
1019+
}
1020+
}
1021+
1022+
// Allocate a local order book
1023+
orderBook := bbgo.NewActiveOrderBook(s.Symbol)
1024+
1025+
// Add all open orders to the local order book
1026+
gridPriceMap := make(map[string]fixedpoint.Value)
1027+
for _, pin := range s.grid.Pins {
1028+
price := fixedpoint.Value(pin)
1029+
gridPriceMap[price.String()] = price
1030+
}
1031+
1032+
// Ensure that orders are grid orders
1033+
// The price must be at the grid pin
1034+
for _, openOrder := range openOrders {
1035+
if _, exists := gridPriceMap[openOrder.Price.String()]; exists {
1036+
orderBook.Add(openOrder)
1037+
}
1038+
}
1039+
1040+
// Note that for MAX Exchange, the order history API only uses fromID parameter to query history order.
1041+
// The time range does not matter.
1042+
closedOrders, err := historyService.QueryClosedOrders(ctx, s.Symbol, firstOrderTime, time.Now(), lastOrderID)
1043+
if err != nil {
1044+
return err
1045+
}
1046+
1047+
// types.SortOrdersAscending()
1048+
// for each closed order, if it's newer than the open order's update time, we will update it.
1049+
for _, closedOrder := range closedOrders {
1050+
// skip non-grid order prices
1051+
if _, ok := gridPriceMap[closedOrder.Price.String()]; !ok {
1052+
continue
1053+
}
1054+
1055+
existingOrder := orderBook.Lookup(func(o types.Order) bool {
1056+
return o.Price.Compare(closedOrder.Price) == 0
1057+
})
1058+
1059+
if existingOrder == nil {
1060+
orderBook.Add(closedOrder)
1061+
} else {
1062+
// TODO: Compare update time and create time
1063+
orderBook.Update(closedOrder)
1064+
}
1065+
}
1066+
1067+
return nil
1068+
}
1069+
9891070
func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
9901071
instanceID := s.InstanceID()
9911072

pkg/types/ordermap.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,29 @@ func (m OrderMap) Backup() (orderForms []SubmitOrder) {
1616
return orderForms
1717
}
1818

19+
// Add the order the the map
1920
func (m OrderMap) Add(o Order) {
2021
m[o.OrderID] = o
2122
}
2223

23-
// Update only updates the order when the order exists in the map
24+
// Update only updates the order when the order ID exists in the map
2425
func (m OrderMap) Update(o Order) {
2526
if _, ok := m[o.OrderID]; ok {
2627
m[o.OrderID] = o
2728
}
2829
}
2930

31+
func (m OrderMap) Lookup(f func(o Order) bool) *Order {
32+
for _, order := range m {
33+
if f(order) {
34+
// copy and return
35+
o := order
36+
return &o
37+
}
38+
}
39+
return nil
40+
}
41+
3042
func (m OrderMap) Remove(orderID uint64) {
3143
delete(m, orderID)
3244
}
@@ -153,6 +165,12 @@ func (m *SyncOrderMap) Exists(orderID uint64) (exists bool) {
153165
return exists
154166
}
155167

168+
func (m *SyncOrderMap) Lookup(f func(o Order) bool) *Order {
169+
m.Lock()
170+
defer m.Unlock()
171+
return m.orders.Lookup(f)
172+
}
173+
156174
func (m *SyncOrderMap) Len() int {
157175
m.Lock()
158176
defer m.Unlock()

0 commit comments

Comments
 (0)