From bdc89ca579432ce7df0f62edc6075168d42089d6 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 7 Nov 2024 16:44:55 +0800 Subject: [PATCH] dynamic: implement Compare() and add tests to Compare() --- pkg/bbgo/livenote.go | 9 ++-- pkg/dynamic/compare.go | 17 ++++--- pkg/dynamic/compare_test.go | 75 +++++++++++++++++++++++++++++ pkg/livenote/options.go | 12 +++++ pkg/notifier/slacknotifier/slack.go | 24 +++------ 5 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 pkg/livenote/options.go diff --git a/pkg/bbgo/livenote.go b/pkg/bbgo/livenote.go index 20f92e367..e51f41668 100644 --- a/pkg/bbgo/livenote.go +++ b/pkg/bbgo/livenote.go @@ -6,21 +6,22 @@ import ( "github.com/c9s/bbgo/pkg/livenote" ) -// PostLiveNote posts a live note to slack or other services +// PostLiveNote a global function helper for strategies to call. +// This function posts a live note to slack or other services // The MessageID will be set after the message is posted if it's not set. -func PostLiveNote(obj livenote.Object) { +func PostLiveNote(obj livenote.Object, opts ...livenote.Option) { if len(Notification.liveNotePosters) == 0 { logrus.Warn("no live note poster is registered") return } for _, poster := range Notification.liveNotePosters { - if err := poster.PostLiveNote(obj); err != nil { + if err := poster.PostLiveNote(obj, opts...); err != nil { logrus.WithError(err).Errorf("unable to post live note: %+v", obj) } } } type LiveNotePoster interface { - PostLiveNote(note livenote.Object) error + PostLiveNote(note livenote.Object, opts ...livenote.Option) error } diff --git a/pkg/dynamic/compare.go b/pkg/dynamic/compare.go index 4ff6890a7..203f98ff8 100644 --- a/pkg/dynamic/compare.go +++ b/pkg/dynamic/compare.go @@ -17,8 +17,6 @@ type Diff struct { // a (after) // b (before) func Compare(a, b interface{}) ([]Diff, error) { - var diffs []Diff - ra := reflect.ValueOf(a) if ra.Kind() == reflect.Ptr { ra = ra.Elem() @@ -56,13 +54,11 @@ func Compare(a, b interface{}) ([]Diff, error) { }, }, nil } + } else if raKind == reflect.Struct { + return compareStruct(ra, rb) } - if raKind == reflect.Struct { - - } - - return diffs, nil + return nil, nil } func compareStruct(a, b reflect.Value) ([]Diff, error) { @@ -93,7 +89,12 @@ func compareStruct(a, b reflect.Value) ([]Diff, error) { fieldValueA := a.Field(i) fieldValueB := b.Field(i) - fieldName := a.Type().Field(i).Name + fieldA := a.Type().Field(i) + fieldName := fieldA.Name + + if !fieldA.IsExported() { + continue + } if isSimpleType(fieldValueA.Kind()) { if compareSimpleValue(fieldValueA, fieldValueB) { diff --git a/pkg/dynamic/compare_test.go b/pkg/dynamic/compare_test.go index b5da05215..36200f545 100644 --- a/pkg/dynamic/compare_test.go +++ b/pkg/dynamic/compare_test.go @@ -55,6 +55,81 @@ func Test_convertToStr(t *testing.T) { }) } +func Test_Compare(t *testing.T) { + tests := []struct { + name string + a, b interface{} + want []Diff + wantErr assert.ErrorAssertionFunc + }{ + { + name: "order", + wantErr: assert.NoError, + a: &types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + Status: types.OrderStatusFilled, + ExecutedQuantity: fixedpoint.NewFromFloat(100.0), + }, + b: &types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Quantity: fixedpoint.NewFromFloat(100.0), + }, + ExecutedQuantity: fixedpoint.NewFromFloat(50.0), + Status: types.OrderStatusPartiallyFilled, + }, + want: []Diff{ + { + Field: "Status", + Before: "PARTIALLY_FILLED", + After: "FILLED", + }, + { + Field: "ExecutedQuantity", + Before: "50", + After: "100", + }, + }, + }, + { + name: "deposit and order", + wantErr: assert.NoError, + a: &types.Deposit{ + Address: "0x6666", + TransactionID: "0x3333", + Status: types.DepositPending, + Confirmation: "10/15", + }, + b: &types.Deposit{ + Address: "0x6666", + TransactionID: "0x3333", + Status: types.DepositPending, + Confirmation: "1/15", + }, + want: []Diff{ + { + Field: "Confirmation", + Before: "1/15", + After: "10/15", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Compare(tt.a, tt.b) + if !tt.wantErr(t, err, fmt.Sprintf("Compare(%v, %v)", tt.a, tt.b)) { + return + } + + assert.Equalf(t, tt.want, got, "Compare(%v, %v)", tt.a, tt.b) + }) + } +} + func Test_compareStruct(t *testing.T) { tests := []struct { name string diff --git a/pkg/livenote/options.go b/pkg/livenote/options.go new file mode 100644 index 000000000..216dddec0 --- /dev/null +++ b/pkg/livenote/options.go @@ -0,0 +1,12 @@ +package livenote + +type Option interface{} + +type Mention struct { + User string +} + +type Comment struct { + Text string + Users []string +} diff --git a/pkg/notifier/slacknotifier/slack.go b/pkg/notifier/slacknotifier/slack.go index db127c8f7..238a6bca7 100644 --- a/pkg/notifier/slacknotifier/slack.go +++ b/pkg/notifier/slacknotifier/slack.go @@ -71,17 +71,7 @@ func (n *Notifier) worker() { } } -type LiveNoteOption interface{} - -type Mention struct { - User string -} - -type Comment struct { - Text string -} - -func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...LiveNoteOption) error { +func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) error { note := n.liveNotePool.Update(obj) ctx := context.Background() @@ -100,15 +90,17 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...LiveNoteOption) err var slackOpts []slack.MsgOption slackOpts = append(slackOpts, slack.MsgOptionAttachments(attachment)) - var mentions []*Mention - var comments []*Comment + var userIds []string + var mentions []*livenote.Mention + var comments []*livenote.Comment for _, opt := range opts { switch val := opt.(type) { - case *Mention: + case *livenote.Mention: mentions = append(mentions, val) - case *Comment: + userIds = append(userIds, val.User) + case *livenote.Comment: comments = append(comments, val) - + userIds = append(userIds, val.Users...) } }