feature: withdraw print config functionality from drift to be a general function

This commit is contained in:
zenix 2022-08-17 13:42:01 +09:00
parent c4a84840e2
commit 5e7ea71613
8 changed files with 214 additions and 116 deletions

View File

@ -1,6 +1,8 @@
package bbgo
import (
"bytes"
"encoding/json"
"reflect"
"github.com/pkg/errors"
@ -35,6 +37,47 @@ type ExitMethod struct {
TrailingStop *TrailingStop2 `json:"trailingStop"`
}
func (e ExitMethod) String() string {
var buf bytes.Buffer
if e.RoiStopLoss != nil {
b, _ := json.Marshal(e.RoiStopLoss)
if string(b) != "null" {
buf.WriteString("roiStopLoss: " + string(b) + ", ")
}
}
if e.ProtectiveStopLoss != nil {
b, _ := json.Marshal(e.ProtectiveStopLoss)
if string(b) != "null" {
buf.WriteString("protectiveStopLoss: " + string(b) + ", ")
}
}
if e.RoiTakeProfit != nil {
b, _ := json.Marshal(e.RoiTakeProfit)
if string(b) != "null" {
buf.WriteString("rioTakeProft: " + string(b) + ", ")
}
}
if e.LowerShadowTakeProfit == nil {
b, _ := json.Marshal(e.LowerShadowTakeProfit)
if string(b) != "null" {
buf.WriteString("lowerShadowTakeProft: " + string(b) + ", ")
}
}
if e.CumulatedVolumeTakeProfit == nil {
b, _ := json.Marshal(e.CumulatedVolumeTakeProfit)
if string(b) != "null" {
buf.WriteString("cumulatedVolumeTakeProfit: " + string(b) + ", ")
}
}
if e.TrailingStop == nil {
b, _ := json.Marshal(e.TrailingStop)
if string(b) != "null" {
buf.WriteString("trailingStop: " + string(b) + ", ")
}
}
return buf.String()
}
// 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{}) {

View File

@ -526,7 +526,7 @@ func (it *CoreInteraction) Initialize() error {
// getStrategySignature returns strategy instance unique signature
func getStrategySignature(strategy SingleExchangeStrategy) (string, error) {
// Returns instance ID
var signature = callID(strategy)
var signature = CallID(strategy)
if signature != "" {
return signature, nil
}

View File

@ -82,7 +82,7 @@ func (p *Persistence) Save(val interface{}, subIDs ...string) error {
}
func (p *Persistence) Sync(obj interface{}) error {
id := callID(obj)
id := CallID(obj)
if len(id) == 0 {
return nil
}
@ -93,7 +93,7 @@ func (p *Persistence) Sync(obj interface{}) error {
// Sync syncs the object properties into the persistence layer
func Sync(obj interface{}) {
id := callID(obj)
id := CallID(obj)
if len(id) == 0 {
log.Warnf("InstanceID() is not provided, can not sync persistence")
return

View File

@ -21,7 +21,6 @@ func (s *TestStructWithoutInstanceID) ID() string {
return "test-struct-no-instance-id"
}
type TestStruct struct {
*Environment
@ -56,15 +55,15 @@ func preparePersistentServices() []service.PersistenceService {
return pss
}
func Test_callID(t *testing.T) {
func Test_CallID(t *testing.T) {
t.Run("default", func(t *testing.T) {
id := callID(&TestStruct{})
id := CallID(&TestStruct{})
assert.NotEmpty(t, id)
assert.Equal(t, "test-struct", id)
})
t.Run("fallback", func(t *testing.T) {
id := callID(&TestStructWithoutInstanceID{Symbol: "BTCUSDT"})
id := CallID(&TestStructWithoutInstanceID{Symbol: "BTCUSDT"})
assert.Equal(t, "test-struct-no-instance-id:BTCUSDT", id)
})
}
@ -122,7 +121,7 @@ func Test_storePersistenceFields(t *testing.T) {
for _, ps := range pss {
psName := reflect.TypeOf(ps).Elem().String()
t.Run("all/"+psName, func(t *testing.T) {
id := callID(a)
id := CallID(a)
err := storePersistenceFields(a, id, ps)
assert.NoError(t, err)

View File

@ -10,7 +10,7 @@ type InstanceIDProvider interface {
InstanceID() string
}
func callID(obj interface{}) string {
func CallID(obj interface{}) string {
sv := reflect.ValueOf(obj)
st := reflect.TypeOf(obj)
if st.Implements(reflect.TypeOf((*InstanceIDProvider)(nil)).Elem()) {
@ -30,4 +30,3 @@ func callID(obj interface{}) string {
ret := m.Call(nil)
return ret[0].String() + ":"
}

View File

@ -384,7 +384,7 @@ func (trader *Trader) LoadState() error {
log.Infof("loading strategies states...")
return trader.IterateStrategies(func(strategy StrategyID) error {
id := callID(strategy)
id := CallID(strategy)
return loadPersistenceFields(strategy, id, ps)
})
}
@ -420,7 +420,7 @@ func (trader *Trader) SaveState() error {
log.Infof("saving strategies states...")
return trader.IterateStrategies(func(strategy StrategyID) error {
id := callID(strategy)
id := CallID(strategy)
if len(id) == 0 {
return nil
}

View File

@ -4,14 +4,10 @@ import (
"fmt"
"io"
"reflect"
"sort"
"strings"
"unsafe"
"github.com/c9s/bbgo/pkg/util"
"github.com/fatih/color"
"github.com/c9s/bbgo/pkg/strategy"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
)
type jsonStruct struct {
@ -26,6 +22,16 @@ func (a jsonArr) Len() int { return len(a) }
func (a jsonArr) Less(i, j int) bool { return a[i].key < a[j].key }
func (a jsonArr) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func canInt(v reflect.Value) bool {
k := v.Type().Kind()
switch k {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
default:
return false
}
}
func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
length := 1
if len(seriesLength) > 0 && seriesLength[0] > 0 {
@ -75,7 +81,7 @@ func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
}
} else if canString {
fmt.Fprintf(f, "%s: %s\n", fieldName, stringFunc.Call(nil)[0].String())
} else if field.CanConvert(reflect.TypeOf(int(0))) {
} else if canInt(field) {
fmt.Fprintf(f, "%s: %d\n", fieldName, field.Int())
} else if field.CanConvert(reflect.TypeOf(float64(0))) {
fmt.Fprintf(f, "%s: %.4f\n", fieldName, field.Float())
@ -107,104 +113,9 @@ func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
}
func (s *Strategy) Print(f io.Writer, pretty bool, withColor ...bool) {
//b, _ := json.MarshalIndent(s.ExitMethods, " ", " ")
t := table.NewWriter()
style := table.Style{
Name: "StyleRounded",
Box: table.StyleBoxRounded,
Color: table.ColorOptionsDefault,
Format: table.FormatOptionsDefault,
HTML: table.DefaultHTMLOptions,
Options: table.OptionsDefault,
Title: table.TitleOptionsDefault,
}
var hiyellow func(io.Writer, string, ...interface{})
if len(withColor) > 0 && withColor[0] {
var style *table.Style
if pretty {
style.Color = table.ColorOptionsYellowWhiteOnBlack
style.Color.Row = text.Colors{text.FgHiYellow, text.BgHiBlack}
style.Color.RowAlternate = text.Colors{text.FgYellow, text.BgBlack}
}
hiyellow = color.New(color.FgHiYellow).FprintfFunc()
} else {
hiyellow = func(a io.Writer, format string, args ...interface{}) {
fmt.Fprintf(a, format, args...)
}
}
if pretty {
t.SetOutputMirror(f)
t.SetStyle(style)
t.AppendHeader(table.Row{"json", "struct field name", "type", "value"})
}
hiyellow(f, "------ %s Settings ------\n", s.InstanceID())
embeddedWhiteSet := map[string]struct{}{"Window": {}, "Interval": {}, "Symbol": {}}
redundantSet := map[string]struct{}{}
var rows []table.Row
val := reflect.ValueOf(*s)
var values jsonArr
for i := 0; i < val.Type().NumField(); i++ {
t := val.Type().Field(i)
if !t.IsExported() {
continue
}
fieldName := t.Name
switch jsonTag := t.Tag.Get("json"); jsonTag {
case "-":
case "":
if t.Anonymous {
var target reflect.Type
if t.Type.Kind() == util.Pointer {
target = t.Type.Elem()
} else {
target = t.Type
}
for j := 0; j < target.NumField(); j++ {
tt := target.Field(j)
if !tt.IsExported() {
continue
}
fieldName := tt.Name
if _, ok := embeddedWhiteSet[fieldName]; !ok {
continue
}
if jtag := tt.Tag.Get("json"); jtag != "" && jtag != "-" {
name := strings.Split(jtag, ",")[0]
if _, ok := redundantSet[name]; ok {
continue
}
redundantSet[name] = struct{}{}
var value interface{}
if t.Type.Kind() == util.Pointer {
value = val.Field(i).Elem().Field(j).Interface()
} else {
value = val.Field(i).Field(j).Interface()
}
values = append(values, jsonStruct{key: fieldName, json: name, tp: tt.Type.String(), value: value})
}
}
}
default:
name := strings.Split(jsonTag, ",")[0]
if _, ok := redundantSet[name]; ok {
continue
}
redundantSet[name] = struct{}{}
values = append(values, jsonStruct{key: fieldName, json: name, tp: t.Type.String(), value: val.Field(i).Interface()})
}
}
sort.Sort(values)
for _, value := range values {
if pretty {
rows = append(rows, table.Row{value.json, value.key, value.tp, value.value})
} else {
hiyellow(f, "%s: %v\n", value.json, value.value)
}
}
if pretty {
t.AppendRows(rows)
t.Render()
style = strategy.DefaultStyle()
}
strategy.PrintConfig(s, f, style, len(withColor) > 0 && withColor[0], strategy.DefaultWhiteList()...)
}

146
pkg/strategy/output.go Normal file
View File

@ -0,0 +1,146 @@
package strategy
import (
"fmt"
"io"
"reflect"
"sort"
"strings"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/util"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
)
type JsonStruct struct {
Key string
Json string
Type string
Value interface{}
}
type JsonArr []JsonStruct
func (a JsonArr) Len() int { return len(a) }
func (a JsonArr) Less(i, j int) bool { return a[i].Key < a[j].Key }
func (a JsonArr) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func DefaultStyle() *table.Style {
style := table.Style{
Name: "StyleRounded",
Box: table.StyleBoxRounded,
Format: table.FormatOptionsDefault,
HTML: table.DefaultHTMLOptions,
Options: table.OptionsDefault,
Title: table.TitleOptionsDefault,
Color: table.ColorOptionsYellowWhiteOnBlack,
}
style.Color.Row = text.Colors{text.FgHiYellow, text.BgHiBlack}
style.Color.RowAlternate = text.Colors{text.FgYellow, text.BgBlack}
return &style
}
func DefaultWhiteList() []string {
return []string{"Window", "Interval", "Symbol"}
}
func PrintConfig(s interface{}, f io.Writer, style *table.Style, withColor bool, whiteLists ...string) {
t := table.NewWriter()
var write func(io.Writer, string, ...interface{})
if withColor {
write = color.New(color.FgHiYellow).FprintfFunc()
} else {
write = func(a io.Writer, format string, args ...interface{}) {
fmt.Fprintf(a, format, args...)
}
}
if style != nil {
t.SetOutputMirror(f)
t.SetStyle(*style)
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 4, WidthMax: 50, WidthMaxEnforcer: text.WrapText},
})
t.AppendHeader(table.Row{"json", "struct field name", "type", "value"})
}
write(f, "---- %s Settings ---\n", bbgo.CallID(s))
embeddedWhiteSet := map[string]struct{}{}
for _, whiteList := range whiteLists {
embeddedWhiteSet[whiteList] = struct{}{}
}
redundantSet := map[string]struct{}{}
var rows []table.Row
val := reflect.ValueOf(s)
if val.Type().Kind() == util.Pointer {
val = val.Elem()
}
var values JsonArr
for i := 0; i < val.Type().NumField(); i++ {
t := val.Type().Field(i)
if !t.IsExported() {
continue
}
fieldName := t.Name
switch jsonTag := t.Tag.Get("json"); jsonTag {
case "-":
case "":
// we only fetch fields from the first layer of the embedded struct
if t.Anonymous {
var target reflect.Type
var field reflect.Value
if t.Type.Kind() == util.Pointer {
target = t.Type.Elem()
field = val.Field(i).Elem()
} else {
target = t.Type
field = val.Field(i)
}
for j := 0; j < target.NumField(); j++ {
tt := target.Field(j)
if !tt.IsExported() {
continue
}
fieldName := tt.Name
if _, ok := embeddedWhiteSet[fieldName]; !ok {
continue
}
if jtag := tt.Tag.Get("json"); jtag != "" && jtag != "-" {
name := strings.Split(jtag, ",")[0]
if _, ok := redundantSet[name]; ok {
continue
}
redundantSet[name] = struct{}{}
value := field.Field(j).Interface()
values = append(values, JsonStruct{Key: fieldName, Json: name, Type: tt.Type.String(), Value: value})
}
}
}
default:
name := strings.Split(jsonTag, ",")[0]
if _, ok := redundantSet[name]; ok {
continue
}
redundantSet[name] = struct{}{}
values = append(values, JsonStruct{Key: fieldName, Json: name, Type: t.Type.String(), Value: val.Field(i).Interface()})
}
}
sort.Sort(values)
for _, value := range values {
if style != nil {
rows = append(rows, table.Row{value.Json, value.Key, value.Type, value.Value})
} else {
write(f, "%s: %v\n", value.Json, value.Value)
}
}
if style != nil {
t.AppendRows(rows)
t.Render()
}
}