all: refactor exit method set and fix dynamic call/merge

Signed-off-by: c9s <yoanlin93@gmail.com>
This commit is contained in:
c9s 2022-06-30 15:13:42 +08:00
parent e2ab363e64
commit b15e8d0ce4
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
7 changed files with 77 additions and 17 deletions

View File

@ -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"))
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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"
@ -87,7 +86,7 @@ type Strategy struct {
ResistanceShort *ResistanceShort `json:"resistanceShort"`
Entry Entry `json:"entry"`
ExitMethods []bbgo.ExitMethod `json:"exits"`
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 {