From b15e8d0ce4614b5e494007ce9ef57a01b2354fd2 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 30 Jun 2022 15:13:42 +0800 Subject: [PATCH] all: refactor exit method set and fix dynamic call/merge Signed-off-by: c9s --- pkg/bbgo/exit.go | 36 ++++++++++++++++++++++- pkg/bbgo/exit_lower_shadow_take_profit.go | 11 ++++++- pkg/dynamic/call.go | 18 ++++++++++-- pkg/dynamic/merge.go | 9 ++++++ pkg/dynamic/merge_test.go | 3 ++ pkg/dynamic/typevalue.go | 4 +-- pkg/strategy/pivotshort/strategy.go | 13 ++------ 7 files changed, 77 insertions(+), 17 deletions(-) diff --git a/pkg/bbgo/exit.go b/pkg/bbgo/exit.go index a0880cf6d..5b99ce982 100644 --- a/pkg/bbgo/exit.go +++ b/pkg/bbgo/exit.go @@ -1,11 +1,25 @@ package bbgo import ( + "reflect" + "github.com/pkg/errors" "github.com/c9s/bbgo/pkg/dynamic" ) +type ExitMethodSet []ExitMethod + +func (s *ExitMethodSet) SetAndSubscribe(session *ExchangeSession, parent interface{}) { + for i := range *s { + m := (*s)[i] + + // manually inherit configuration from strategy + m.Inherit(parent) + m.Subscribe(session) + } +} + type ExitMethod struct { RoiStopLoss *RoiStopLoss `json:"roiStopLoss"` ProtectiveStopLoss *ProtectiveStopLoss `json:"protectiveStopLoss"` @@ -14,9 +28,29 @@ type ExitMethod struct { CumulatedVolumeTakeProfit *CumulatedVolumeTakeProfit `json:"cumulatedVolumeTakeProfit"` } +// Inherit is used for inheriting properties from the given strategy struct +// for example, some exit method requires the default interval and symbol name from the strategy param object +func (m *ExitMethod) Inherit(parent interface{}) { + // we need to pass some information from the strategy configuration to the exit methods, like symbol, interval and window + rt := reflect.TypeOf(m).Elem() + rv := reflect.ValueOf(m).Elem() + for j := 0; j < rv.NumField(); j++ { + if !rt.Field(j).IsExported() { + continue + } + + fieldValue := rv.Field(j) + if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { + continue + } + + dynamic.MergeStructValues(fieldValue.Interface(), parent) + } +} + func (m *ExitMethod) Subscribe(session *ExchangeSession) { if err := dynamic.CallStructFieldsMethod(m, "Subscribe", session); err != nil { - panic(errors.Wrap(err, "dynamic call failed")) + panic(errors.Wrap(err, "dynamic Subscribe call failed")) } } diff --git a/pkg/bbgo/exit_lower_shadow_take_profit.go b/pkg/bbgo/exit_lower_shadow_take_profit.go index a6008223e..677bbd908 100644 --- a/pkg/bbgo/exit_lower_shadow_take_profit.go +++ b/pkg/bbgo/exit_lower_shadow_take_profit.go @@ -8,12 +8,21 @@ import ( ) type LowerShadowTakeProfit struct { - Ratio fixedpoint.Value `json:"ratio"` + // inherit from the strategy + types.IntervalWindow + // inherit from the strategy + Symbol string `json:"symbol"` + + Ratio fixedpoint.Value `json:"ratio"` session *ExchangeSession orderExecutor *GeneralOrderExecutor } +func (s *LowerShadowTakeProfit) Subscribe(session *ExchangeSession) { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) +} + func (s *LowerShadowTakeProfit) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) { s.session = session s.orderExecutor = orderExecutor diff --git a/pkg/dynamic/call.go b/pkg/dynamic/call.go index aa43c6f9d..a3122faad 100644 --- a/pkg/dynamic/call.go +++ b/pkg/dynamic/call.go @@ -25,16 +25,28 @@ func CallStructFieldsMethod(m interface{}, method string, args ...interface{}) e argValues := ToReflectValues(args...) for i := 0; i < rt.NumField(); i++ { fieldType := rt.Field(i) + fieldValue := rv.Field(i) // skip non-exported fields if !fieldType.IsExported() { continue } - if _, ok := fieldType.Type.MethodByName(method); ok { - refMethod := rv.Field(i).MethodByName(method) - refMethod.Call(argValues) + if fieldType.Type.Kind() == reflect.Ptr && fieldValue.IsNil() { + continue } + + methodType, ok := fieldType.Type.MethodByName(method) + if !ok { + continue + } + + if len(argValues) < methodType.Type.NumIn() { + // return fmt.Errorf("method %v require %d args, %d given", methodType, methodType.Type.NumIn(), len(argValues)) + } + + refMethod := fieldValue.MethodByName(method) + refMethod.Call(argValues) } return nil diff --git a/pkg/dynamic/merge.go b/pkg/dynamic/merge.go index ac20f8152..7d87939e8 100644 --- a/pkg/dynamic/merge.go +++ b/pkg/dynamic/merge.go @@ -5,6 +5,10 @@ import "reflect" // MergeStructValues merges the field value from the source struct to the dest struct. // Only fields with the same type and the same name will be updated. func MergeStructValues(dst, src interface{}) { + if dst == nil { + return + } + rtA := reflect.TypeOf(dst) srcStructType := reflect.TypeOf(src) @@ -14,6 +18,11 @@ func MergeStructValues(dst, src interface{}) { for i := 0; i < rtA.NumField(); i++ { fieldType := rtA.Field(i) fieldName := fieldType.Name + + if !fieldType.IsExported() { + continue + } + // if there is a field with the same name if fieldSrcType, ok := srcStructType.FieldByName(fieldName); ok { // ensure that the type is the same diff --git a/pkg/dynamic/merge_test.go b/pkg/dynamic/merge_test.go index bc65e49c8..be7c2ee25 100644 --- a/pkg/dynamic/merge_test.go +++ b/pkg/dynamic/merge_test.go @@ -36,8 +36,10 @@ func Test_reflectMergeStructFields(t *testing.T) { iw := types.IntervalWindow{Interval: types.Interval1h, Window: 30} a := &struct { types.IntervalWindow + Symbol string }{ IntervalWindow: iw, + Symbol: "BTCUSDT", } b := &struct { Symbol string @@ -45,6 +47,7 @@ func Test_reflectMergeStructFields(t *testing.T) { }{} MergeStructValues(b, a) assert.Equal(t, iw, b.IntervalWindow) + assert.Equal(t, "BTCUSDT", b.Symbol) }) t.Run("non-zero embedded struct", func(t *testing.T) { diff --git a/pkg/dynamic/typevalue.go b/pkg/dynamic/typevalue.go index a12ccf416..3ca3f1c83 100644 --- a/pkg/dynamic/typevalue.go +++ b/pkg/dynamic/typevalue.go @@ -15,10 +15,10 @@ func NewTypeValueInterface(typ reflect.Type) interface{} { // ToReflectValues convert the go objects into reflect.Value slice func ToReflectValues(args ...interface{}) (values []reflect.Value) { - for _, arg := range args { + for i := range args { + arg := args[i] values = append(values, reflect.ValueOf(arg)) } return values } - diff --git a/pkg/strategy/pivotshort/strategy.go b/pkg/strategy/pivotshort/strategy.go index 4d8f87338..5cdb40661 100644 --- a/pkg/strategy/pivotshort/strategy.go +++ b/pkg/strategy/pivotshort/strategy.go @@ -10,7 +10,6 @@ import ( "github.com/sirupsen/logrus" "github.com/c9s/bbgo/pkg/bbgo" - "github.com/c9s/bbgo/pkg/dynamic" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/indicator" "github.com/c9s/bbgo/pkg/types" @@ -86,8 +85,8 @@ type Strategy struct { ResistanceShort *ResistanceShort `json:"resistanceShort"` - Entry Entry `json:"entry"` - ExitMethods []bbgo.ExitMethod `json:"exits"` + Entry Entry `json:"entry"` + ExitMethods bbgo.ExitMethodSet `json:"exits"` session *bbgo.ExchangeSession orderExecutor *bbgo.GeneralOrderExecutor @@ -120,13 +119,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{}) } - for i := range s.ExitMethods { - m := s.ExitMethods[i] - - // we need to pass some information from the strategy configuration to the exit methods, like symbol, interval and window - dynamic.MergeStructValues(&m, s) - m.Subscribe(session) - } + s.ExitMethods.SetAndSubscribe(session, s) } func (s *Strategy) InstanceID() string {