diff --git a/pkg/dynamic/compare_test.go b/pkg/dynamic/compare_test.go index 36200f545..a4bf3133d 100644 --- a/pkg/dynamic/compare_test.go +++ b/pkg/dynamic/compare_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/c9s/bbgo/pkg/fixedpoint" + . "github.com/c9s/bbgo/pkg/testing/testhelper" "github.com/c9s/bbgo/pkg/types" ) @@ -94,6 +95,52 @@ func Test_Compare(t *testing.T) { }, }, }, + { + name: "kline", + wantErr: assert.NoError, + a: types.KLine{ + Open: Number(60000), + High: Number(61000), + Low: Number(59500), + Close: Number(60100), + }, + b: types.KLine{ + Open: Number(60000), + High: Number(61000), + Low: Number(59500), + Close: Number(60200), + }, + want: []Diff{ + { + Field: "Close", + Before: "60200", + After: "60100", + }, + }, + }, + { + name: "kline ptr", + wantErr: assert.NoError, + a: &types.KLine{ + Open: Number(60000), + High: Number(61000), + Low: Number(59500), + Close: Number(60100), + }, + b: &types.KLine{ + Open: Number(60000), + High: Number(61000), + Low: Number(59500), + Close: Number(60200), + }, + want: []Diff{ + { + Field: "Close", + Before: "60200", + After: "60100", + }, + }, + }, { name: "deposit and order", wantErr: assert.NoError, diff --git a/pkg/livenote/livenote.go b/pkg/livenote/livenote.go index 3cac0c3cc..af01cef95 100644 --- a/pkg/livenote/livenote.go +++ b/pkg/livenote/livenote.go @@ -56,6 +56,21 @@ func NewPool(size int64) *Pool { } } +func (p *Pool) Get(obj Object) *LiveNote { + objID := obj.ObjectID() + + p.mu.Lock() + defer p.mu.Unlock() + + for _, note := range p.notes { + if note.ObjectID() == objID { + return note + } + } + + return nil +} + func (p *Pool) Update(obj Object) *LiveNote { objID := obj.ObjectID() diff --git a/pkg/notifier/slacknotifier/slack.go b/pkg/notifier/slacknotifier/slack.go index dad711459..ef938b05f 100644 --- a/pkg/notifier/slacknotifier/slack.go +++ b/pkg/notifier/slacknotifier/slack.go @@ -11,6 +11,7 @@ import ( "golang.org/x/time/rate" + "github.com/c9s/bbgo/pkg/dynamic" "github.com/c9s/bbgo/pkg/livenote" "github.com/c9s/bbgo/pkg/types" @@ -229,27 +230,12 @@ func (n *Notifier) translateHandle(ctx context.Context, handle string) (string, } func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) error { - note := n.liveNotePool.Update(obj) - ctx := n.ctx - - channel := note.ChannelID - if channel == "" { - channel = n.channel - } - - var attachment slack.Attachment - if creator, ok := note.Object.(types.SlackAttachmentCreator); ok { - attachment = creator.SlackAttachment() - } else { - return fmt.Errorf("livenote object does not support types.SlackAttachmentCreator interface") - } - var slackOpts []slack.MsgOption - slackOpts = append(slackOpts, slack.MsgOptionAttachments(attachment)) var firstTimeHandles []string var commentHandles []string var comments []string + var shouldCompare bool for _, opt := range opts { switch val := opt.(type) { case *livenote.OptionOneTimeMention: @@ -257,22 +243,57 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) er case *livenote.OptionComment: comments = append(comments, val.Text) commentHandles = append(commentHandles, val.Users...) + case *livenote.OptionCompare: + shouldCompare = val.Value + } + } + + var ctx = n.ctx + var curObj, prevObj any + if shouldCompare { + if prevNote := n.liveNotePool.Get(obj); prevNote != nil { + prevObj = prevNote.Object + } + } + + channel := n.channel + note := n.liveNotePool.Update(obj) + curObj = note.Object + + if shouldCompare && prevObj != nil { + diffs, err := dynamic.Compare(curObj, prevObj) + if err != nil { + log.WithError(err).Warnf("unable to compare objects: %T and %T", curObj, prevObj) + } else { + if comment := diffsToComment(curObj, diffs); len(comment) > 0 { + comments = append(comments, comment) + } } } - firstTimeTags, err := n.translateHandles(context.Background(), firstTimeHandles) + if note.ChannelID != "" { + channel = note.ChannelID + } + + var attachment slack.Attachment + if creator, ok := note.Object.(types.SlackAttachmentCreator); ok { + attachment = creator.SlackAttachment() + } else { + return fmt.Errorf("livenote object does not support types.SlackAttachmentCreator interface") + } + + slackOpts = append(slackOpts, slack.MsgOptionAttachments(attachment)) + + firstTimeTags, err := n.translateHandles(n.ctx, firstTimeHandles) if err != nil { return err } - commentTags, err := n.translateHandles(context.Background(), commentHandles) + commentTags, err := n.translateHandles(n.ctx, commentHandles) if err != nil { return err } - // format: mention slack user - // <@U012AB3CD> - if note.MessageID != "" { // If compare is enabled, we need to attach the comments @@ -312,7 +333,7 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) er note.SetMessageID(respTs) if len(firstTimeTags) > 0 { - n.queueTask(context.Background(), notifyTask{ + n.queueTask(n.ctx, notifyTask{ channel: respCh, threadTs: respTs, opts: []slack.MsgOption{ @@ -321,7 +342,7 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) er }, }, 100*time.Millisecond) } - + if len(comments) > 0 { var text string if len(commentTags) > 0 { @@ -329,7 +350,7 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) er } text += joinComments(comments) - n.queueTask(context.Background(), notifyTask{ + n.queueTask(n.ctx, notifyTask{ channel: respCh, threadTs: respTs, opts: []slack.MsgOption{ @@ -463,3 +484,17 @@ func joinTags(tags []string) string { func joinComments(comments []string) string { return strings.Join(comments, "\n") } + +func diffsToComment(obj any, diffs []dynamic.Diff) (text string) { + if len(diffs) == 0 { + return text + } + + text += fmt.Sprintf("%T updated\n", obj) + + for _, diff := range diffs { + text += fmt.Sprintf("- %s: `%s` transited to `%s`\n", diff.Field, diff.Before, diff.After) + } + + return text +} diff --git a/pkg/strategy/example/livenote/strategy.go b/pkg/strategy/example/livenote/strategy.go index 341c906e2..be0aa868a 100644 --- a/pkg/strategy/example/livenote/strategy.go +++ b/pkg/strategy/example/livenote/strategy.go @@ -65,7 +65,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se log.Info(k) bbgo.PostLiveNote(&k, livenote.OneTimeMention(s.UserID), - livenote.Comment("please check the deposit", s.UserID), + livenote.Comment("please check the deposit"), livenote.CompareObject(true)) }