2020-07-10 13:34:39 +00:00
|
|
|
package bbgo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-13 05:25:48 +00:00
|
|
|
"time"
|
2020-07-13 04:57:18 +00:00
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
2020-07-12 11:44:05 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/bbgo/exchange/binance"
|
|
|
|
"github.com/c9s/bbgo/pkg/bbgo/types"
|
2020-07-10 13:34:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Trader struct {
|
2020-07-13 05:25:48 +00:00
|
|
|
Notifier *SlackNotifier
|
2020-07-12 14:57:51 +00:00
|
|
|
|
2020-07-10 13:34:39 +00:00
|
|
|
// Context is trading Context
|
|
|
|
Context *TradingContext
|
|
|
|
|
2020-07-11 05:02:53 +00:00
|
|
|
Exchange *binance.Exchange
|
2020-07-13 05:25:48 +00:00
|
|
|
|
|
|
|
reportTimer *time.Timer
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Strategy interface {
|
|
|
|
Init(trader *Trader, stream *binance.PrivateStream) error
|
|
|
|
}
|
|
|
|
|
2020-07-13 05:31:40 +00:00
|
|
|
func (t *Trader) RunStrategy(ctx context.Context, strategy Strategy) (chan struct{}, error) {
|
2020-07-13 05:25:48 +00:00
|
|
|
symbol := t.Context.Symbol
|
|
|
|
|
|
|
|
stream, err := t.Exchange.NewPrivateStream()
|
|
|
|
if err != nil {
|
2020-07-13 05:31:40 +00:00
|
|
|
return nil, err
|
2020-07-13 05:25:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := strategy.Init(t, stream); err != nil {
|
2020-07-13 05:31:40 +00:00
|
|
|
return nil, err
|
2020-07-13 05:25:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
t.reportTimer = time.AfterFunc(1*time.Second, func() {
|
|
|
|
t.ReportPnL()
|
|
|
|
})
|
|
|
|
|
|
|
|
stream.OnTrade(func(trade *types.Trade) {
|
|
|
|
if trade.Symbol != symbol {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t.ReportTrade(trade)
|
|
|
|
t.Context.ProfitAndLossCalculator.AddTrade(*trade)
|
|
|
|
|
|
|
|
if t.reportTimer != nil {
|
|
|
|
t.reportTimer.Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
t.reportTimer = time.AfterFunc(5*time.Second, func() {
|
|
|
|
t.ReportPnL()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
stream.OnKLineEvent(func(e *binance.KLineEvent) {
|
|
|
|
t.Context.SetCurrentPrice(e.KLine.GetClose())
|
|
|
|
})
|
|
|
|
|
|
|
|
var eventC = make(chan interface{}, 20)
|
|
|
|
if err := stream.Connect(ctx, eventC); err != nil {
|
2020-07-13 05:31:40 +00:00
|
|
|
return nil, err
|
2020-07-13 05:25:48 +00:00
|
|
|
}
|
|
|
|
|
2020-07-13 05:31:40 +00:00
|
|
|
done := make(chan struct{})
|
|
|
|
|
2020-07-13 05:25:48 +00:00
|
|
|
go func() {
|
2020-07-13 05:31:40 +00:00
|
|
|
defer close(done)
|
2020-07-13 05:25:48 +00:00
|
|
|
defer stream.Close()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
|
|
|
|
// drain the event channel
|
|
|
|
case <-eventC:
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-07-13 05:31:40 +00:00
|
|
|
return done, nil
|
2020-07-10 13:34:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Trader) Infof(format string, args ...interface{}) {
|
2020-07-13 05:25:48 +00:00
|
|
|
t.Notifier.Notify(format, args...)
|
2020-07-10 13:34:39 +00:00
|
|
|
}
|
|
|
|
|
2020-07-12 11:44:05 +00:00
|
|
|
func (t *Trader) ReportTrade(trade *types.Trade) {
|
2020-07-12 17:01:33 +00:00
|
|
|
t.Notifier.ReportTrade(trade)
|
2020-07-10 13:34:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Trader) ReportPnL() {
|
2020-07-12 14:52:37 +00:00
|
|
|
report := t.Context.ProfitAndLossCalculator.Calculate()
|
2020-07-11 03:23:48 +00:00
|
|
|
report.Print()
|
2020-07-12 17:01:33 +00:00
|
|
|
t.Notifier.ReportPnL(report)
|
2020-07-10 13:34:39 +00:00
|
|
|
}
|
|
|
|
|
2020-07-12 11:44:05 +00:00
|
|
|
func (t *Trader) SubmitOrder(ctx context.Context, order *types.Order) {
|
2020-07-13 05:25:48 +00:00
|
|
|
t.Notifier.Notify(":memo: Submitting %s order on side %s with volume: %s", order.Type, order.Side, order.VolumeStr, order.SlackAttachment())
|
2020-07-10 13:34:39 +00:00
|
|
|
|
|
|
|
err := t.Exchange.SubmitOrder(ctx, order)
|
|
|
|
if err != nil {
|
2020-07-13 04:30:35 +00:00
|
|
|
log.WithError(err).Errorf("order create error: side %s volume: %s", order.Side, order.VolumeStr)
|
2020-07-10 13:34:39 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|