fix: SourceSelector json marshal/unmarshal without Init from strategy. smartCancel check on order status

This commit is contained in:
zenix 2022-09-06 19:08:05 +09:00
parent 67e57b49eb
commit 36a5579660
7 changed files with 104 additions and 24 deletions

View File

@ -23,7 +23,7 @@ import (
"github.com/c9s/bbgo/pkg/util"
)
var KLineLimit int64 = 1000
var KLinePreloadLimit int64 = 1000
// ExchangeSession presents the exchange connection Session
// It also maintains and collects the data returned from the stream.
@ -421,7 +421,7 @@ func (session *ExchangeSession) initSymbol(ctx context.Context, environ *Environ
// avoid querying the last unclosed kline
endTime := environ.startTime
var i int64
for i = 0; i < KLineLimit; i += 1000 {
for i = 0; i < KLinePreloadLimit; i += 1000 {
var duration time.Duration = time.Duration(-i * int64(interval.Duration()))
e := endTime.Add(duration)
@ -441,7 +441,6 @@ func (session *ExchangeSession) initSymbol(ctx context.Context, environ *Environ
// update last prices by the given kline
lastKLine := kLines[len(kLines)-1]
if interval == types.Interval1m {
log.Infof("last kline %+v", lastKLine)
session.lastPrices[symbol] = lastKLine.Close
}

View File

@ -1,6 +1,7 @@
package bbgo
import (
"encoding/json"
"strings"
"github.com/c9s/bbgo/pkg/fixedpoint"
@ -10,16 +11,32 @@ import (
type SourceFunc func(*types.KLine) fixedpoint.Value
var Four fixedpoint.Value = fixedpoint.NewFromInt(4)
var Three fixedpoint.Value = fixedpoint.NewFromInt(3)
var Two fixedpoint.Value = fixedpoint.NewFromInt(2)
type SourceSelector struct {
Source string `json:"source,omitempty"`
type selectorInternal struct {
Source string
getSource SourceFunc
}
func (s *SourceSelector) Init() {
func (s *selectorInternal) UnmarshalJSON(d []byte) error {
if err := json.Unmarshal(d, &s.Source); err != nil {
return err
}
s.init()
return nil
}
func (s selectorInternal) MarshalJSON() ([]byte, error) {
if s.Source == "" {
s.Source = "close"
s.init()
}
return []byte("\"" + s.Source + "\""), nil
}
type SourceSelector struct {
Source selectorInternal `json:"source,omitempty"`
}
func (s *selectorInternal) init() {
switch strings.ToLower(s.Source) {
case "close":
s.getSource = func(kline *types.KLine) fixedpoint.Value { return kline.Close }
@ -28,23 +45,36 @@ func (s *SourceSelector) Init() {
case "low":
s.getSource = func(kline *types.KLine) fixedpoint.Value { return kline.Low }
case "hl2":
s.getSource = func(kline *types.KLine) fixedpoint.Value { return kline.High.Add(kline.Low).Div(Two) }
s.getSource = func(kline *types.KLine) fixedpoint.Value { return kline.High.Add(kline.Low).Div(fixedpoint.Two) }
case "hlc3":
s.getSource = func(kline *types.KLine) fixedpoint.Value {
return kline.High.Add(kline.Low).Add(kline.Close).Div(Three)
return kline.High.Add(kline.Low).Add(kline.Close).Div(fixedpoint.Three)
}
case "ohlc4":
s.getSource = func(kline *types.KLine) fixedpoint.Value {
return kline.High.Add(kline.Low).Add(kline.Close).Add(kline.Open).Div(Four)
return kline.High.Add(kline.Low).Add(kline.Close).Add(kline.Open).Div(fixedpoint.Four)
}
case "open":
s.getSource = func(kline *types.KLine) fixedpoint.Value { return kline.Open }
default:
log.Infof("source not set: %s, use hl2 by default", s.Source)
s.getSource = func(kline *types.KLine) fixedpoint.Value { return kline.High.Add(kline.Low).Div(Two) }
s.getSource = func(kline *types.KLine) fixedpoint.Value { return kline.High.Add(kline.Low).Div(fixedpoint.Two) }
}
}
func (s *SourceSelector) GetSource(kline *types.KLine) fixedpoint.Value {
return s.getSource(kline)
func (s *selectorInternal) String() string {
if s.Source == "" {
s.Source = "close"
s.init()
}
return s.Source
}
// lazy init if empty struct is passed in
func (s *SourceSelector) GetSource(kline *types.KLine) fixedpoint.Value {
if s.Source.Source == "" {
s.Source.Source = "close"
s.Source.init()
}
return s.Source.getSource(kline)
}

34
pkg/bbgo/source_test.go Normal file
View File

@ -0,0 +1,34 @@
package bbgo
import (
"encoding/json"
"testing"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/stretchr/testify/assert"
)
func TestSource(t *testing.T) {
input := "{\"source\":\"high\"}"
type Strategy struct {
SourceSelector
}
s := Strategy{}
assert.NoError(t, json.Unmarshal([]byte(input), &s))
assert.Equal(t, s.Source.Source, "high")
assert.NotNil(t, s.Source.getSource)
e, err := json.Marshal(&s)
assert.NoError(t, err)
assert.Equal(t, input, string(e))
input = "{}"
s = Strategy{}
assert.NoError(t, json.Unmarshal([]byte(input), &s))
assert.Equal(t, fixedpoint.Zero, s.GetSource(&types.KLine{}))
e, err = json.Marshal(&Strategy{})
assert.NoError(t, err)
assert.Equal(t, "{\"source\":\"close\"}", string(e))
}

View File

@ -1,6 +1,7 @@
package dynamic
import (
"encoding/json"
"fmt"
"io"
"reflect"
@ -16,7 +17,7 @@ import (
)
func DefaultWhiteList() []string {
return []string{"Window", "Interval", "Symbol"}
return []string{"Window", "RightWindow", "Interval", "Symbol", "Source"}
}
// @param s: strategy object
@ -96,6 +97,9 @@ func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool,
}
redundantSet[name] = struct{}{}
value := field.Field(j).Interface()
if e, err := json.Marshal(value); err == nil {
value = string(e)
}
values = append(values, types.JsonStruct{Key: fieldName, Json: name, Type: tt.Type.String(), Value: value})
}
}
@ -106,7 +110,11 @@ func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool,
continue
}
redundantSet[name] = struct{}{}
values = append(values, types.JsonStruct{Key: fieldName, Json: name, Type: t.Type.String(), Value: val.Field(i).Interface()})
value := val.Field(i).Interface()
if e, err := json.Marshal(value); err == nil {
value = string(e)
}
values = append(values, types.JsonStruct{Key: fieldName, Json: name, Type: t.Type.String(), Value: value})
}
}
sort.Sort(values)

7
pkg/fixedpoint/const.go Normal file
View File

@ -0,0 +1,7 @@
package fixedpoint
var (
Two Value = NewFromInt(2)
Three Value = NewFromInt(3)
Four Value = NewFromInt(3)
)

View File

@ -126,7 +126,7 @@ func (s *Strategy) InstanceID() string {
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
// by default, bbgo only pre-subscribe 1000 klines.
// this is not enough if we're subscribing 30m intervals using SerialMarketDataStore
bbgo.KLineLimit = int64((s.Interval.Minutes()*s.Window/1000 + 1) * 1000)
bbgo.KLinePreloadLimit = int64((s.Interval.Minutes()*s.Window/1000 + 1) * 1000)
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
Interval: types.Interval1m,
})
@ -255,6 +255,9 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e
drift := s.drift1m.Array(2)
for _, order := range nonTraded {
if order.Status != types.OrderStatusNew && order.Status != types.OrderStatusPartiallyFilled {
continue
}
log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.minutesCounter)
if s.minutesCounter-s.orderPendingCounter[order.OrderID] > s.PendingMinutes {
if order.Side == types.SideTypeBuy && drift[1] < drift[0] {
@ -801,8 +804,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
// StrategyController
s.Status = types.StrategyStatusRunning
// Get source function from config input
s.SourceSelector.Init()
s.OnSuspend(func() {
_ = s.GeneralOrderExecutor.GracefulCancel(ctx)

View File

@ -99,7 +99,7 @@ func (s *Strategy) InstanceID() string {
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
// by default, bbgo only pre-subscribe 1000 klines.
// this is not enough if we're subscribing 30m intervals using SerialMarketDataStore
bbgo.KLineLimit = int64((s.Interval.Minutes()*s.WindowSlow/1000 + 1) + 1000)
bbgo.KLinePreloadLimit = int64((s.Interval.Minutes()*s.WindowSlow/1000 + 1) + 1000)
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
Interval: types.Interval1m,
})
@ -178,6 +178,9 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef float64) int {
if len(nonTraded) > 0 {
left := 0
for _, order := range nonTraded {
if order.Status != types.OrderStatusNew && order.Status != types.OrderStatusPartiallyFilled {
continue
}
log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.minutesCounter)
toCancel := false
if s.minutesCounter-s.orderPendingCounter[order.OrderID] >= s.PendingMinutes {
@ -291,8 +294,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
// StrategyController
s.Status = types.StrategyStatusRunning
// Get source function from config input
s.SourceSelector.Init()
s.OnSuspend(func() {
_ = s.GeneralOrderExecutor.GracefulCancel(ctx)
})