all: add more livenote support

This commit is contained in:
c9s 2024-11-05 17:34:26 +08:00
parent 0e2eb8ba54
commit 73b3c5b6dd
No known key found for this signature in database
GPG Key ID: 7385E7E464CB0A54
17 changed files with 67 additions and 19 deletions

View File

@ -0,0 +1,16 @@
---
notifications:
slack:
defaultChannel: "dev-bbgo"
errorChannel: "dev-bbgo"
sessions:
binance:
exchange: binance
envVarPrefix: binance
exchangeStrategies:
- on: binance
livenote:
symbol: BTCUSDT
interval: 5m

View File

@ -22,4 +22,4 @@ Setup Telegram/Slack notification before using Price Alert Strategy. See [Settin
#### Examples #### Examples
See [pricealert.yaml](../../config/pricealert.yaml) and [pricealert-tg.yaml](../../config/pricealert-tg.yaml) See [pricealert.yaml](../../config/example/pricealert.yaml) and [pricealert-tg.yaml](../../config/pricealert-tg.yaml)

View File

@ -9,6 +9,11 @@ import (
// PostLiveNote posts a live note to slack or other services // 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. // 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) {
if len(Notification.liveNotePosters) == 0 {
logrus.Warn("no live note poster is registered")
return
}
for _, poster := range Notification.liveNotePosters { for _, poster := range Notification.liveNotePosters {
if err := poster.PostLiveNote(obj); err != nil { if err := poster.PostLiveNote(obj); err != nil {
logrus.WithError(err).Errorf("unable to post live note: %+v", obj) logrus.WithError(err).Errorf("unable to post live note: %+v", obj)

View File

@ -17,6 +17,7 @@ import (
_ "github.com/c9s/bbgo/pkg/strategy/emastop" _ "github.com/c9s/bbgo/pkg/strategy/emastop"
_ "github.com/c9s/bbgo/pkg/strategy/etf" _ "github.com/c9s/bbgo/pkg/strategy/etf"
_ "github.com/c9s/bbgo/pkg/strategy/ewoDgtrd" _ "github.com/c9s/bbgo/pkg/strategy/ewoDgtrd"
_ "github.com/c9s/bbgo/pkg/strategy/example/kline"
_ "github.com/c9s/bbgo/pkg/strategy/example/livenote" _ "github.com/c9s/bbgo/pkg/strategy/example/livenote"
_ "github.com/c9s/bbgo/pkg/strategy/example/pricealert" _ "github.com/c9s/bbgo/pkg/strategy/example/pricealert"
_ "github.com/c9s/bbgo/pkg/strategy/example/pricedrop" _ "github.com/c9s/bbgo/pkg/strategy/example/pricedrop"
@ -30,7 +31,6 @@ import (
_ "github.com/c9s/bbgo/pkg/strategy/grid2" _ "github.com/c9s/bbgo/pkg/strategy/grid2"
_ "github.com/c9s/bbgo/pkg/strategy/harmonic" _ "github.com/c9s/bbgo/pkg/strategy/harmonic"
_ "github.com/c9s/bbgo/pkg/strategy/irr" _ "github.com/c9s/bbgo/pkg/strategy/irr"
_ "github.com/c9s/bbgo/pkg/strategy/kline"
_ "github.com/c9s/bbgo/pkg/strategy/linregmaker" _ "github.com/c9s/bbgo/pkg/strategy/linregmaker"
_ "github.com/c9s/bbgo/pkg/strategy/liquiditymaker" _ "github.com/c9s/bbgo/pkg/strategy/liquiditymaker"
_ "github.com/c9s/bbgo/pkg/strategy/marketcap" _ "github.com/c9s/bbgo/pkg/strategy/marketcap"

View File

@ -33,6 +33,10 @@ func (n *LiveNote) ObjectID() string {
return n.cachedObjID return n.cachedObjID
} }
func (n *LiveNote) SetObject(object Object) {
n.Object = object
}
func (n *LiveNote) SetMessageID(messageID string) { func (n *LiveNote) SetMessageID(messageID string) {
n.MessageID = messageID n.MessageID = messageID
} }
@ -46,9 +50,9 @@ type Pool struct {
mu sync.Mutex mu sync.Mutex
} }
func NewPool() *Pool { func NewPool(size int64) *Pool {
return &Pool{ return &Pool{
notes: make(map[string]*LiveNote, 100), notes: make(map[string]*LiveNote, size),
} }
} }
@ -60,6 +64,8 @@ func (p *Pool) Update(obj Object) *LiveNote {
for _, note := range p.notes { for _, note := range p.notes {
if note.ObjectID() == objID { if note.ObjectID() == objID {
// update the object inside the note
note.SetObject(obj)
return note return note
} }
} }

View File

@ -11,7 +11,7 @@ import (
func TestLiveNotePool(t *testing.T) { func TestLiveNotePool(t *testing.T) {
t.Run("same-kline", func(t *testing.T) { t.Run("same-kline", func(t *testing.T) {
pool := NewPool() pool := NewPool(100)
k := &types.KLine{ k := &types.KLine{
Symbol: "BTCUSDT", Symbol: "BTCUSDT",
Interval: types.Interval1m, Interval: types.Interval1m,
@ -24,7 +24,7 @@ func TestLiveNotePool(t *testing.T) {
}) })
t.Run("different-kline", func(t *testing.T) { t.Run("different-kline", func(t *testing.T) {
pool := NewPool() pool := NewPool(100)
k := &types.KLine{ k := &types.KLine{
Symbol: "BTCUSDT", Symbol: "BTCUSDT",
Interval: types.Interval1m, Interval: types.Interval1m,

View File

@ -27,15 +27,18 @@ type Notifier struct {
channel string channel string
taskC chan notifyTask taskC chan notifyTask
liveNotePool *livenote.Pool
} }
type NotifyOption func(notifier *Notifier) type NotifyOption func(notifier *Notifier)
func New(client *slack.Client, channel string, options ...NotifyOption) *Notifier { func New(client *slack.Client, channel string, options ...NotifyOption) *Notifier {
notifier := &Notifier{ notifier := &Notifier{
channel: channel, channel: channel,
client: client, client: client,
taskC: make(chan notifyTask, 100), taskC: make(chan notifyTask, 100),
liveNotePool: livenote.NewPool(100),
} }
for _, o := range options { for _, o := range options {
@ -69,8 +72,7 @@ func (n *Notifier) worker() {
} }
func (n *Notifier) PostLiveNote(obj livenote.Object) error { func (n *Notifier) PostLiveNote(obj livenote.Object) error {
// TODO: maintain the object pool for live notes note := n.liveNotePool.Update(obj)
var note = livenote.NewLiveNote(obj)
ctx := context.Background() ctx := context.Background()
channel := note.ChannelID channel := note.ChannelID
@ -78,16 +80,25 @@ func (n *Notifier) PostLiveNote(obj livenote.Object) error {
channel = n.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")
}
opts := slack.MsgOptionAttachments(attachment)
if note.MessageID != "" { if note.MessageID != "" {
// UpdateMessageContext returns channel, timestamp, text, err // UpdateMessageContext returns channel, timestamp, text, err
_, _, _, err := n.client.UpdateMessageContext(ctx, channel, note.MessageID, slack.MsgOptionText(note.ObjectID(), true)) _, _, _, err := n.client.UpdateMessageContext(ctx, channel, note.MessageID, opts)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
respCh, respTs, err := n.client.PostMessageContext(ctx, channel) respCh, respTs, err := n.client.PostMessageContext(ctx, channel, opts)
if err != nil { if err != nil {
log.WithError(err). log.WithError(err).
WithField("channel", n.channel). WithField("channel", n.channel).

View File

@ -50,7 +50,8 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
// This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed // This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
callback := func(k types.KLine) { callback := func(k types.KLine) {
// bbgo.PostLiveNote(k) log.Info(k)
bbgo.PostLiveNote(&k)
} }
// register our kline event handler // register our kline event handler

View File

@ -6,8 +6,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/slack-go/slack" "github.com/slack-go/slack"
"github.com/c9s/bbgo/pkg/fixedpoint"
) )
type DepositStatus string type DepositStatus string
@ -64,6 +65,10 @@ func (d Deposit) EffectiveTime() time.Time {
return d.Time.Time() return d.Time.Time()
} }
func (d *Deposit) ObjectID() string {
return "deposit-" + d.Exchange.String() + "-" + d.Asset + "-" + d.Address + "-" + d.TransactionID
}
func (d Deposit) String() (o string) { func (d Deposit) String() (o string) {
o = fmt.Sprintf("%s deposit %s %v <- ", d.Exchange, d.Asset, d.Amount) o = fmt.Sprintf("%s deposit %s %v <- ", d.Exchange, d.Asset, d.Amount)

View File

@ -284,8 +284,9 @@ func (k *KLine) SlackAttachment() slack.Attachment {
Short: true, Short: true,
}, },
}, },
Footer: "",
FooterIcon: "", FooterIcon: ExchangeFooterIcon(k.Exchange),
Footer: k.StartTime.String() + " ~ " + k.EndTime.String(),
} }
} }

View File

@ -317,6 +317,10 @@ func (o Order) CsvRecords() [][]string {
} }
} }
func (o *Order) ObjectID() string {
return "order-" + o.Exchange.String() + "-" + o.Symbol + "-" + strconv.FormatUint(o.OrderID, 10)
}
// Backup backs up the current order quantity to a SubmitOrder object // Backup backs up the current order quantity to a SubmitOrder object
// so that we can post the order later when we want to restore the orders. // so that we can post the order later when we want to restore the orders.
func (o Order) Backup() SubmitOrder { func (o Order) Backup() SubmitOrder {

View File

@ -208,7 +208,6 @@ func (trade Trade) SlackAttachment() slack.Attachment {
liquidity := trade.Liquidity() liquidity := trade.Liquidity()
text := templateutil.Render(slackTradeTextTemplate, trade) text := templateutil.Render(slackTradeTextTemplate, trade)
footerIcon := ExchangeFooterIcon(trade.Exchange)
return slack.Attachment{ return slack.Attachment{
Text: text, Text: text,
@ -225,7 +224,7 @@ func (trade Trade) SlackAttachment() slack.Attachment {
{Title: "Liquidity", Value: liquidity, Short: true}, {Title: "Liquidity", Value: liquidity, Short: true},
{Title: "Order ID", Value: strconv.FormatUint(trade.OrderID, 10), Short: true}, {Title: "Order ID", Value: strconv.FormatUint(trade.OrderID, 10), Short: true},
}, },
FooterIcon: footerIcon, FooterIcon: ExchangeFooterIcon(trade.Exchange),
Footer: strings.ToLower(trade.Exchange.String()) + templateutil.Render(" creation time {{ . }}", trade.Time.Time().Format(time.StampMilli)), Footer: strings.ToLower(trade.Exchange.String()) + templateutil.Render(" creation time {{ . }}", trade.Time.Time().Format(time.StampMilli)),
} }
} }