From 705380204169a222c1dbc7e9f9e4e3e3435cf725 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 12 Jan 2022 02:54:13 +0800 Subject: [PATCH] basic interaction parser --- doc/strategy/interaction.md | 32 +++++++++++ pkg/bbgo/interact.go | 106 ++++++++++++++++++++++++++++++++++++ pkg/bbgo/interact_test.go | 35 ++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 doc/strategy/interaction.md create mode 100644 pkg/bbgo/interact.go create mode 100644 pkg/bbgo/interact_test.go diff --git a/doc/strategy/interaction.md b/doc/strategy/interaction.md new file mode 100644 index 000000000..7f5935e99 --- /dev/null +++ b/doc/strategy/interaction.md @@ -0,0 +1,32 @@ +# Interaction + +In your strategy, you can register your messenger interaction by commands. + + +``` +package mymaker + +import ( + "github.com/c9s/bbgo/pkg/bbgo" +) + +func init() { + bbgo.RegisterInteraction(&MyInteraction{}) +} + +type MyInteraction struct {} + +func (m *MyInteraction) Commands(interact bbgo.Interact) { + interact.Command("closePosition", func(w bbgo.InteractWriter, symbol string, percentage float64) { + + }) +} + + +type Strategy struct {} +``` + + +The interaction engine parses the command from the messenger software programs like Telegram or Slack. +And then pass the arguments to the command handler defined in the strategy. + diff --git a/pkg/bbgo/interact.go b/pkg/bbgo/interact.go new file mode 100644 index 000000000..e355468f0 --- /dev/null +++ b/pkg/bbgo/interact.go @@ -0,0 +1,106 @@ +package bbgo + +import ( + "errors" + "go/scanner" + "go/token" + "reflect" + "strconv" + + "gopkg.in/tucnak/telebot.v2" +) + +// Interact implements the interaction between bot and message software. +type Interact struct { + Commands map[string]func() +} + +func (i *Interact) HandleTelegramMessage(msg *telebot.Message) { + +} + +func parseCommand(text string) (args []string, err error) { + src := []byte(text) + + // Initialize the scanner. + var errHandler scanner.ErrorHandler = func(pos token.Position, msg string) { + err = errors.New(msg) + } + + var s scanner.Scanner + fset := token.NewFileSet() // positions are relative to fset + file := fset.AddFile("", fset.Base(), len(src)) // register input "file" + s.Init(file, src, errHandler, 0) + + // Repeated calls to Scan yield the token sequence found in the input. + for { + _, tok, lit := s.Scan() + if tok == token.EOF { + break + } + + // we are not using the token type right now, but we will use them later + args = append(args, lit) + } + + return args, nil +} + +func parseFuncArgsAndCall(f interface{}, args []string) error { + fv := reflect.ValueOf(f) + ft := reflect.TypeOf(f) + + var rArgs []reflect.Value + for i := 0; i < ft.NumIn(); i++ { + at := ft.In(i) + + switch k := at.Kind(); k { + + case reflect.String: + av := reflect.ValueOf(args[i]) + rArgs = append(rArgs, av) + + case reflect.Bool: + bv, err := strconv.ParseBool(args[i]) + if err != nil { + return err + } + av := reflect.ValueOf(bv) + rArgs = append(rArgs, av) + + case reflect.Int64: + nf, err := strconv.ParseInt(args[i], 10, 64) + if err != nil { + return err + } + + av := reflect.ValueOf(nf) + rArgs = append(rArgs, av) + + case reflect.Float64: + nf, err := strconv.ParseFloat(args[i], 64) + if err != nil { + return err + } + + av := reflect.ValueOf(nf) + rArgs = append(rArgs, av) + } + } + + out := fv.Call(rArgs) + if ft.NumOut() > 0 { + outType := ft.Out(0) + switch outType.Kind() { + case reflect.Interface: + o := out[0].Interface() + switch ov := o.(type) { + case error: + return ov + + } + + } + } + return nil +} diff --git a/pkg/bbgo/interact_test.go b/pkg/bbgo/interact_test.go new file mode 100644 index 000000000..1435dc0f9 --- /dev/null +++ b/pkg/bbgo/interact_test.go @@ -0,0 +1,35 @@ +package bbgo + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_parseFuncArgsAndCall_NoErrorFunction(t *testing.T) { + noErrorFunc := func(a string, b float64, c bool) error { + assert.Equal(t, "BTCUSDT", a) + assert.Equal(t, 0.123, b) + assert.Equal(t, true, c) + return nil + } + + err := parseFuncArgsAndCall(noErrorFunc, []string{"BTCUSDT", "0.123", "true"}) + assert.NoError(t, err) +} + +func Test_parseFuncArgsAndCall_ErrorFunction(t *testing.T) { + errorFunc := func(a string, b float64) error { + return errors.New("error") + } + + err := parseFuncArgsAndCall(errorFunc, []string{"BTCUSDT", "0.123"}) + assert.Error(t, err) + +} + +func Test_parseCommand(t *testing.T) { + +} +