livenote: add test and generate diff summary for object change

This commit is contained in:
c9s 2024-11-11 16:07:04 +08:00
parent c4c6a73774
commit c289c2daf5
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
4 changed files with 125 additions and 28 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
. "github.com/c9s/bbgo/pkg/testing/testhelper"
"github.com/c9s/bbgo/pkg/types" "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", name: "deposit and order",
wantErr: assert.NoError, wantErr: assert.NoError,

View File

@ -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 { func (p *Pool) Update(obj Object) *LiveNote {
objID := obj.ObjectID() objID := obj.ObjectID()

View File

@ -11,6 +11,7 @@ import (
"golang.org/x/time/rate" "golang.org/x/time/rate"
"github.com/c9s/bbgo/pkg/dynamic"
"github.com/c9s/bbgo/pkg/livenote" "github.com/c9s/bbgo/pkg/livenote"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
@ -229,12 +230,49 @@ func (n *Notifier) translateHandle(ctx context.Context, handle string) (string,
} }
func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) error { func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) error {
note := n.liveNotePool.Update(obj) var slackOpts []slack.MsgOption
ctx := n.ctx
channel := note.ChannelID var firstTimeHandles []string
if channel == "" { var commentHandles []string
channel = n.channel var comments []string
var shouldCompare bool
for _, opt := range opts {
switch val := opt.(type) {
case *livenote.OptionOneTimeMention:
firstTimeHandles = append(firstTimeHandles, val.Users...)
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)
}
}
}
if note.ChannelID != "" {
channel = note.ChannelID
} }
var attachment slack.Attachment var attachment slack.Attachment
@ -244,35 +282,18 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) er
return fmt.Errorf("livenote object does not support types.SlackAttachmentCreator interface") return fmt.Errorf("livenote object does not support types.SlackAttachmentCreator interface")
} }
var slackOpts []slack.MsgOption
slackOpts = append(slackOpts, slack.MsgOptionAttachments(attachment)) slackOpts = append(slackOpts, slack.MsgOptionAttachments(attachment))
var firstTimeHandles []string firstTimeTags, err := n.translateHandles(n.ctx, firstTimeHandles)
var commentHandles []string
var comments []string
for _, opt := range opts {
switch val := opt.(type) {
case *livenote.OptionOneTimeMention:
firstTimeHandles = append(firstTimeHandles, val.Users...)
case *livenote.OptionComment:
comments = append(comments, val.Text)
commentHandles = append(commentHandles, val.Users...)
}
}
firstTimeTags, err := n.translateHandles(context.Background(), firstTimeHandles)
if err != nil { if err != nil {
return err return err
} }
commentTags, err := n.translateHandles(context.Background(), commentHandles) commentTags, err := n.translateHandles(n.ctx, commentHandles)
if err != nil { if err != nil {
return err return err
} }
// format: mention slack user
// <@U012AB3CD>
if note.MessageID != "" { if note.MessageID != "" {
// If compare is enabled, we need to attach the comments // 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) note.SetMessageID(respTs)
if len(firstTimeTags) > 0 { if len(firstTimeTags) > 0 {
n.queueTask(context.Background(), notifyTask{ n.queueTask(n.ctx, notifyTask{
channel: respCh, channel: respCh,
threadTs: respTs, threadTs: respTs,
opts: []slack.MsgOption{ opts: []slack.MsgOption{
@ -329,7 +350,7 @@ func (n *Notifier) PostLiveNote(obj livenote.Object, opts ...livenote.Option) er
} }
text += joinComments(comments) text += joinComments(comments)
n.queueTask(context.Background(), notifyTask{ n.queueTask(n.ctx, notifyTask{
channel: respCh, channel: respCh,
threadTs: respTs, threadTs: respTs,
opts: []slack.MsgOption{ opts: []slack.MsgOption{
@ -463,3 +484,17 @@ func joinTags(tags []string) string {
func joinComments(comments []string) string { func joinComments(comments []string) string {
return strings.Join(comments, "\n") 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
}

View File

@ -65,7 +65,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
log.Info(k) log.Info(k)
bbgo.PostLiveNote(&k, bbgo.PostLiveNote(&k,
livenote.OneTimeMention(s.UserID), livenote.OneTimeMention(s.UserID),
livenote.Comment("please check the deposit", s.UserID), livenote.Comment("please check the deposit"),
livenote.CompareObject(true)) livenote.CompareObject(true))
} }