diff --git a/README.md b/README.md index bc2bfd80d..b895d1c53 100644 --- a/README.md +++ b/README.md @@ -392,10 +392,10 @@ persistence: Check out the strategy directory [strategy](pkg/strategy) for all built-in strategies: -- `pricealert` strategy demonstrates how to use the notification system [pricealert](pkg/strategy/pricealert). See +- `pricealert` strategy demonstrates how to use the notification system [pricealert](pkg/strategy/example/pricealert). See [document](./doc/strategy/pricealert.md). - `buyandhold` strategy demonstrates how to subscribe kline events and submit market - order [buyandhold](pkg/strategy/pricedrop) + order [buyandhold](pkg/strategy/example/pricedrop) - `bollgrid` strategy implements a basic grid strategy with the built-in bollinger indicator [bollgrid](pkg/strategy/bollgrid) - `grid` strategy implements the fixed price band grid strategy [grid](pkg/strategy/grid). See diff --git a/README.zh_TW.md b/README.zh_TW.md index c0ae1396e..08bc6dcb3 100644 --- a/README.zh_TW.md +++ b/README.zh_TW.md @@ -370,8 +370,8 @@ persistence: 查看策略目錄 [strategy](pkg/strategy) 以獲得所有內置策略: -- `pricealert` 策略演示如何使用通知系統 [pricealert](pkg/strategy/pricealert)。參見[文件](./doc/strategy/pricealert.md). -- `buyandhold` 策略演示如何訂閱 kline 事件並提交市場訂單 [buyandhold](pkg/strategy/pricedrop) +- `pricealert` 策略演示如何使用通知系統 [pricealert](pkg/strategy/example/pricealert)。參見[文件](./doc/strategy/pricealert.md). +- `buyandhold` 策略演示如何訂閱 kline 事件並提交市場訂單 [buyandhold](pkg/strategy/example/pricedrop) - `bollgrid` 策略實現了一個基本的網格策略,使用內置的布林通道指標 [bollgrid](pkg/strategy/bollgrid) - `grid` 策略實現了固定價格帶網格策略 [grid](pkg/strategy/grid)。參見[文件](./doc/strategy/grid.md). - `supertrend` 策略使用 Supertrend 指標作為趨勢,並使用 DEMA 指標作為噪聲 @@ -621,4 +621,4 @@ make embed && go run -tags web ./cmd/bbgo-lorca ## 授權 -AGPL 授權 \ No newline at end of file +AGPL 授權 diff --git a/pkg/bbgo/livenote.go b/pkg/bbgo/livenote.go new file mode 100644 index 000000000..935ecc9ef --- /dev/null +++ b/pkg/bbgo/livenote.go @@ -0,0 +1,21 @@ +package bbgo + +import ( + "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/types" +) + +// PostLiveNote 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(note *types.LiveNote) { + for _, poster := range Notification.liveNotePosters { + if err := poster.PostLiveNote(note); err != nil { + logrus.WithError(err).Errorf("unable to post live note: %+v", note) + } + } +} + +type LiveNotePoster interface { + PostLiveNote(note *types.LiveNote) error +} diff --git a/pkg/bbgo/notification.go b/pkg/bbgo/notification.go index 2d8420297..aff860517 100644 --- a/pkg/bbgo/notification.go +++ b/pkg/bbgo/notification.go @@ -48,7 +48,9 @@ func (n *NullNotifier) SendPhoto(buffer *bytes.Buffer) {} func (n *NullNotifier) SendPhotoTo(channel string, buffer *bytes.Buffer) {} type Notifiability struct { - notifiers []Notifier + notifiers []Notifier + liveNotePosters []LiveNotePoster + SessionChannelRouter *PatternChannelRouter `json:"-"` SymbolChannelRouter *PatternChannelRouter `json:"-"` ObjectChannelRouter *ObjectChannelRouter `json:"-"` @@ -81,6 +83,10 @@ func (m *Notifiability) RouteObject(obj interface{}) (channel string, ok bool) { // AddNotifier adds the notifier that implements the Notifier interface. func (m *Notifiability) AddNotifier(notifier Notifier) { m.notifiers = append(m.notifiers, notifier) + + if poster, ok := notifier.(LiveNotePoster); ok { + m.liveNotePosters = append(m.liveNotePosters, poster) + } } func (m *Notifiability) Notify(obj interface{}, args ...interface{}) { diff --git a/pkg/notifier/slacknotifier/slack.go b/pkg/notifier/slacknotifier/slack.go index 2ac9a1fa0..4b1f6851a 100644 --- a/pkg/notifier/slacknotifier/slack.go +++ b/pkg/notifier/slacknotifier/slack.go @@ -54,7 +54,8 @@ func (n *Notifier) worker() { return case task := <-n.taskC: - limiter.Wait(ctx) + // ignore the wait error + _ = limiter.Wait(ctx) _, _, err := n.client.PostMessageContext(ctx, task.Channel, task.Opts...) if err != nil { @@ -66,7 +67,40 @@ func (n *Notifier) worker() { } } +func (n *Notifier) PostLiveNote(note *types.LiveNote) error { + ctx := context.Background() + + channel := note.ChannelID + if channel == "" { + channel = n.channel + } + + if note.MessageID != "" { + // UpdateMessageContext returns channel, timestamp, text, err + _, _, _, err := n.client.UpdateMessageContext(ctx, channel, note.MessageID, slack.MsgOptionText(note.ObjectID(), true)) + if err != nil { + return err + } + + } else { + + respCh, respTs, err := n.client.PostMessageContext(ctx, channel) + if err != nil { + log.WithError(err). + WithField("channel", n.channel). + Errorf("slack api error: %s", err.Error()) + return err + } + + note.SetChannelID(respCh) + note.SetMessageID(respTs) + } + + return nil +} + func (n *Notifier) Notify(obj interface{}, args ...interface{}) { + // TODO: filter args for the channel option n.NotifyTo(n.channel, obj, args...) } diff --git a/pkg/strategy/pricealert/strategy.go b/pkg/strategy/example/pricealert/strategy.go similarity index 100% rename from pkg/strategy/pricealert/strategy.go rename to pkg/strategy/example/pricealert/strategy.go diff --git a/pkg/strategy/pricedrop/strategy.go b/pkg/strategy/example/pricedrop/strategy.go similarity index 100% rename from pkg/strategy/pricedrop/strategy.go rename to pkg/strategy/example/pricedrop/strategy.go diff --git a/pkg/strategy/rsicross/strategy.go b/pkg/strategy/example/rsicross/strategy.go similarity index 100% rename from pkg/strategy/rsicross/strategy.go rename to pkg/strategy/example/rsicross/strategy.go diff --git a/pkg/strategy/skeleton/strategy.go b/pkg/strategy/example/skeleton/strategy.go similarity index 100% rename from pkg/strategy/skeleton/strategy.go rename to pkg/strategy/example/skeleton/strategy.go diff --git a/pkg/types/livenote.go b/pkg/types/livenote.go new file mode 100644 index 000000000..b5d328a7d --- /dev/null +++ b/pkg/types/livenote.go @@ -0,0 +1,33 @@ +package types + +type LiveNoteObject interface { + ObjectID() string +} + +type LiveNote struct { + // MessageID is the unique identifier of the message + // for slack, it's the timestamp of the message + MessageID string `json:"messageId"` + + ChannelID string `json:"channelId"` + + Object LiveNoteObject +} + +func NewLiveNote(object LiveNoteObject) *LiveNote { + return &LiveNote{ + Object: object, + } +} + +func (n *LiveNote) ObjectID() string { + return n.Object.ObjectID() +} + +func (n *LiveNote) SetMessageID(messageID string) { + n.MessageID = messageID +} + +func (n *LiveNote) SetChannelID(channelID string) { + n.ChannelID = channelID +}