mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 14:55:16 +00:00
add livenote pool and livenote example strategy
This commit is contained in:
parent
e310ea9e4e
commit
9b77980a69
|
@ -3,19 +3,19 @@ package bbgo
|
||||||
import (
|
import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/livenote"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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(note *types.LiveNote) {
|
func PostLiveNote(obj livenote.Object) {
|
||||||
for _, poster := range Notification.liveNotePosters {
|
for _, poster := range Notification.liveNotePosters {
|
||||||
if err := poster.PostLiveNote(note); err != nil {
|
if err := poster.PostLiveNote(obj); err != nil {
|
||||||
logrus.WithError(err).Errorf("unable to post live note: %+v", note)
|
logrus.WithError(err).Errorf("unable to post live note: %+v", obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type LiveNotePoster interface {
|
type LiveNotePoster interface {
|
||||||
PostLiveNote(note *types.LiveNote) error
|
PostLiveNote(note livenote.Object) error
|
||||||
}
|
}
|
||||||
|
|
73
pkg/livenote/livenote.go
Normal file
73
pkg/livenote/livenote.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package livenote
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type Object 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 Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLiveNote(object Object) *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
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pool struct {
|
||||||
|
notes map[string]*LiveNote
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPool() *Pool {
|
||||||
|
return &Pool{
|
||||||
|
notes: make(map[string]*LiveNote, 100),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) Update(obj Object) *LiveNote {
|
||||||
|
objID := obj.ObjectID()
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
for _, note := range p.notes {
|
||||||
|
if note.ObjectID() == objID {
|
||||||
|
return note
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
note := NewLiveNote(obj)
|
||||||
|
p.add(objID, note)
|
||||||
|
return note
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) add(id string, note *LiveNote) {
|
||||||
|
p.notes[id] = note
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool) Add(note *LiveNote) {
|
||||||
|
p.mu.Lock()
|
||||||
|
p.add(note.ObjectID(), note)
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
44
pkg/livenote/livenote_test.go
Normal file
44
pkg/livenote/livenote_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package livenote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLiveNotePool(t *testing.T) {
|
||||||
|
t.Run("same-kline", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
k := &types.KLine{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Interval: types.Interval1m,
|
||||||
|
StartTime: types.Time(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
|
}
|
||||||
|
|
||||||
|
note := pool.Update(k)
|
||||||
|
note2 := pool.Update(k)
|
||||||
|
assert.Equal(t, note, note2, "the returned note object should be the same")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different-kline", func(t *testing.T) {
|
||||||
|
pool := NewPool()
|
||||||
|
k := &types.KLine{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Interval: types.Interval1m,
|
||||||
|
StartTime: types.Time(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)),
|
||||||
|
}
|
||||||
|
|
||||||
|
k2 := &types.KLine{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Interval: types.Interval1m,
|
||||||
|
StartTime: types.Time(time.Date(2021, 1, 1, 0, 1, 0, 0, time.UTC)),
|
||||||
|
}
|
||||||
|
|
||||||
|
note := pool.Update(k)
|
||||||
|
note2 := pool.Update(k2)
|
||||||
|
assert.NotEqual(t, note, note2, "the returned note object should be different")
|
||||||
|
})
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/livenote"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -67,7 +68,9 @@ func (n *Notifier) worker() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) PostLiveNote(note *types.LiveNote) error {
|
func (n *Notifier) PostLiveNote(obj livenote.Object) error {
|
||||||
|
// TODO: maintain the object pool for live notes
|
||||||
|
var note = livenote.NewLiveNote(obj)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
channel := note.ChannelID
|
channel := note.ChannelID
|
||||||
|
|
67
pkg/strategy/example/livenote/strategy.go
Normal file
67
pkg/strategy/example/livenote/strategy.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package livenote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID is the unique strategy ID, it needs to be in all lower case
|
||||||
|
// For example, grid strategy uses "grid"
|
||||||
|
const ID = "livenote"
|
||||||
|
|
||||||
|
// log is a logrus.Entry that will be reused.
|
||||||
|
// This line attaches the strategy field to the logger with our ID, so that the logs from this strategy will be tagged with our ID
|
||||||
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
|
||||||
|
var ten = fixedpoint.NewFromInt(10)
|
||||||
|
|
||||||
|
// init is a special function of golang, it will be called when the program is started
|
||||||
|
// importing this package will trigger the init function call.
|
||||||
|
func init() {
|
||||||
|
// Register our struct type to BBGO
|
||||||
|
// Note that you don't need to field the fields.
|
||||||
|
// BBGO uses reflect to parse your type information.
|
||||||
|
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy is a struct that contains the settings of your strategy.
|
||||||
|
// These settings will be loaded from the BBGO YAML config file "bbgo.yaml" automatically.
|
||||||
|
type Strategy struct {
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) ID() string {
|
||||||
|
return ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) InstanceID() string {
|
||||||
|
return ID + "-" + s.Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: "1m"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
callback := func(k types.KLine) {
|
||||||
|
// bbgo.PostLiveNote(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// register our kline event handler
|
||||||
|
session.MarketDataStream.OnKLine(callback)
|
||||||
|
session.MarketDataStream.OnKLineClosed(callback)
|
||||||
|
|
||||||
|
// if you need to do something when the user data stream is ready
|
||||||
|
// note that you only receive order update, trade update, balance update when the user data stream is connect.
|
||||||
|
session.UserDataStream.OnStart(func() {
|
||||||
|
log.Infof("connected")
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -74,6 +74,10 @@ type KLine struct {
|
||||||
Closed bool `json:"closed" db:"closed"`
|
Closed bool `json:"closed" db:"closed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *KLine) ObjectID() string {
|
||||||
|
return "kline-" + k.Symbol + k.Interval.String() + k.StartTime.Time().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
func (k *KLine) Set(o *KLine) {
|
func (k *KLine) Set(o *KLine) {
|
||||||
k.GID = o.GID
|
k.GID = o.GID
|
||||||
k.Exchange = o.Exchange
|
k.Exchange = o.Exchange
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user