Merge pull request #1034 from c9s/feature/grid2

strategy: grid2: use initial order ID to query closed order history to recover grid
This commit is contained in:
Yo-An Lin 2022-12-26 16:14:13 +08:00 committed by GitHub
commit 5bb590faec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 43 additions and 115 deletions

View File

@ -24,6 +24,7 @@ type GridProfitStats struct {
Market types.Market `json:"market,omitempty"`
ProfitEntries []*GridProfit `json:"profitEntries,omitempty"`
Since *time.Time `json:"since,omitempty"`
InitialOrderID uint64 `json:"initialOrderID"`
}
func newGridProfitStats(market types.Market) *GridProfitStats {

View File

@ -3,6 +3,7 @@ package grid2
import (
"context"
"fmt"
"sort"
"strconv"
"sync"
"time"
@ -116,7 +117,6 @@ type Strategy struct {
SkipSpreadCheck bool `json:"skipSpreadCheck"`
GridProfitStats *GridProfitStats `persistence:"grid_profit_stats"`
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
Position *types.Position `persistence:"position"`
grid *Grid
@ -179,7 +179,15 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
// InstanceID returns the instance identifier from the current grid configuration parameters
func (s *Strategy) InstanceID() string {
return fmt.Sprintf("%s-%s-%d-%d-%d", ID, s.Symbol, s.GridNum, s.UpperPrice.Int(), s.LowerPrice.Int())
id := fmt.Sprintf("%s-%s-size-%d", ID, s.Symbol, s.GridNum)
if s.AutoRange != nil {
id += "-autoRange-" + s.AutoRange.String()
} else {
id += "-" + s.UpperPrice.String() + "-" + s.LowerPrice.String()
}
return id
}
func (s *Strategy) checkSpread() error {
@ -820,10 +828,23 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
return err
}
var orderIds []uint64
for _, order := range createdOrders {
orderIds = append(orderIds, order.OrderID)
s.logger.Info(order.String())
}
sort.Slice(orderIds, func(i, j int) bool {
return orderIds[i] < orderIds[j]
})
if len(orderIds) > 0 {
s.GridProfitStats.InitialOrderID = orderIds[0]
bbgo.Sync(ctx, s)
}
s.logger.Infof("ALL GRID ORDERS SUBMITTED")
return nil
}
@ -998,9 +1019,13 @@ func (s *Strategy) recoverGrid(ctx context.Context, historyService types.Exchang
_ = lastOrderTime
// for MAX exchange we need the order ID to query the closed order history
if s.GridProfitStats != nil && s.GridProfitStats.InitialOrderID > 0 {
lastOrderID = s.GridProfitStats.InitialOrderID
} else {
if oid, ok := findEarliestOrderID(openOrders); ok {
lastOrderID = oid
}
}
activeOrderBook := s.orderExecutor.ActiveMakerOrders()
@ -1251,10 +1276,6 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
s.GridProfitStats = newGridProfitStats(s.Market)
}
if s.ProfitStats == nil {
s.ProfitStats = types.NewProfitStats(s.Market)
}
if s.Position == nil {
s.Position = types.NewPositionFromMarket(s.Market)
}
@ -1276,7 +1297,6 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
orderExecutor := bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
orderExecutor.BindEnvironment(s.Environment)
orderExecutor.BindProfitStats(s.ProfitStats)
orderExecutor.Bind()
orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _, _ fixedpoint.Value) {
s.GridProfitStats.AddTrade(trade)
@ -1338,14 +1358,14 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
session.MarketDataStream.OnKLineClosed(s.newTakeProfitHandler(ctx, session))
}
session.UserDataStream.OnStart(func() {
// if TriggerPrice is zero, that means we need to open the grid when start up
if s.TriggerPrice.IsZero() {
session.UserDataStream.OnStart(func() {
if err := s.openGrid(ctx, session); err != nil {
s.logger.WithError(err).Errorf("failed to setup grid orders")
}
return
}
})
}
return nil
}

View File

@ -52,7 +52,7 @@ func (s *State) SlackAttachment() slack.Attachment {
Title: s.Asset + " Transfer States",
Fields: []slack.AttachmentField{
{Title: "Total Number of Transfers", Value: fmt.Sprintf("%d", s.DailyNumberOfTransfers), Short: true},
{Title: "Total Amount of Transfers", Value: util.FormatFloat(s.DailyAmountOfTransfers.Float64(), 4), Short: true},
{Title: "Total Amount of Transfers", Value: s.DailyAmountOfTransfers.String(), Short: true},
},
Footer: templateutil.Render("Since {{ . }}", time.Unix(s.Since, 0).Format(time.RFC822)),
}

View File

@ -10,7 +10,7 @@ import (
"github.com/pkg/errors"
)
var simpleDurationRegExp = regexp.MustCompile("^(\\d+)([hdw])$")
var simpleDurationRegExp = regexp.MustCompile(`^(\d+)([hdw])$`)
var ErrNotSimpleDuration = errors.New("the given input is not simple duration format, valid format: [1-9][0-9]*[hdw]")
@ -20,6 +20,10 @@ type SimpleDuration struct {
Duration Duration
}
func (d *SimpleDuration) String() string {
return fmt.Sprintf("%d%s", d.Num, d.Unit)
}
func (d *SimpleDuration) Interval() Interval {
switch d.Unit {

View File

@ -1,60 +0,0 @@
package util
import (
"math"
"strconv"
"github.com/c9s/bbgo/pkg/fixedpoint"
)
const MaxDigits = 18 // MAX_INT64 ~ 9 * 10^18
var Pow10Table = [MaxDigits + 1]int64{
1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18,
}
func Pow10(n int64) int64 {
if n < 0 || n > MaxDigits {
return 0
}
return Pow10Table[n]
}
func FormatValue(val fixedpoint.Value, prec int) string {
return val.FormatString(prec)
}
func FormatFloat(val float64, prec int) string {
return strconv.FormatFloat(val, 'f', prec, 64)
}
func ParseFloat(s string) (float64, error) {
if len(s) == 0 {
return 0.0, nil
}
return strconv.ParseFloat(s, 64)
}
func MustParseFloat(s string) float64 {
if len(s) == 0 {
return 0.0
}
v, err := strconv.ParseFloat(s, 64)
if err != nil {
panic(err)
}
return v
}
const epsilon = 0.0000001
func Zero(v float64) bool {
return math.Abs(v) < epsilon
}
func NotZero(v float64) bool {
return math.Abs(v) > epsilon
}

View File

@ -1,36 +1 @@
package util
import "testing"
func TestNotZero(t *testing.T) {
type args struct {
v float64
}
tests := []struct {
name string
args args
want bool
}{
{
name: "0",
args: args{
v: 0,
},
want: false,
},
{
name: "0.00001",
args: args{
v: 0.00001,
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NotZero(tt.args.v); got != tt.want {
t.Errorf("NotZero() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -1,2 +0,0 @@
package util