bbgo_origin/bbgo/trader.go

286 lines
6.9 KiB
Go
Raw Normal View History

2020-07-10 13:34:39 +00:00
package bbgo
import (
"context"
2020-07-16 07:36:02 +00:00
"fmt"
2020-07-13 16:20:15 +00:00
"github.com/c9s/bbgo/pkg/util"
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
)
2020-07-14 06:54:23 +00:00
type Strategy interface {
Init(tradingContext *TradingContext, trader types.Trader) error
OnNewStream(stream *types.StandardPrivateStream) error
2020-07-14 06:54:23 +00:00
}
type KLineRegressionTrader struct {
// Context is trading Context
2020-07-16 07:36:02 +00:00
Context *TradingContext
SourceKLines []types.KLine
ProfitAndLossCalculator *ProfitAndLossCalculator
2020-07-20 03:49:20 +00:00
doneOrders []*types.Order
pendingOrders []*types.Order
}
func (trader *KLineRegressionTrader) SubmitOrder(cxt context.Context, order *types.Order) {
2020-07-20 03:49:20 +00:00
trader.pendingOrders = append(trader.pendingOrders, order)
}
2020-07-16 07:36:02 +00:00
func (trader *KLineRegressionTrader) RunStrategy(ctx context.Context, strategy Strategy) (chan struct{}, error) {
log.Infof("[regression] number of kline data: %d", len(trader.SourceKLines))
2020-07-20 03:49:20 +00:00
maxExposure := 0.4
trader.Context.Quota = make(map[string]types.Balance)
for currency, balance := range trader.Context.Balances {
quota := balance
quota.Available *= maxExposure
trader.Context.Quota[ currency ] = quota
}
2020-07-16 07:36:02 +00:00
done := make(chan struct{})
defer close(done)
2020-07-16 07:36:02 +00:00
if err := strategy.Init(trader.Context, trader); err != nil {
return nil, err
}
standardStream := types.StandardPrivateStream{}
if err := strategy.OnNewStream(&standardStream); err != nil {
return nil, err
}
2020-07-16 07:36:02 +00:00
var tradeID int64 = 0
for _, kline := range trader.SourceKLines {
2020-07-16 07:36:02 +00:00
log.Debugf("kline %+v", kline)
fmt.Print(".")
standardStream.EmitKLineClosed(&kline)
2020-07-16 07:36:02 +00:00
2020-07-20 03:49:20 +00:00
for _, order := range trader.pendingOrders {
2020-07-18 12:35:37 +00:00
switch order.Side {
case types.SideTypeBuy:
fmt.Print("B")
case types.SideTypeSell:
fmt.Print("S")
}
2020-07-16 07:36:02 +00:00
var price float64
if order.Type == types.OrderTypeLimit {
price = util.MustParseFloat(order.PriceStr)
} else {
price = kline.GetClose()
}
volume := util.MustParseFloat(order.VolumeStr)
fee := 0.0
feeCurrency := ""
trader.Context.Lock()
2020-07-16 07:36:02 +00:00
if order.Side == types.SideTypeBuy {
fee = price * volume * 0.001
feeCurrency = "USDT"
2020-07-16 10:25:40 +00:00
quote := trader.Context.Balances[trader.Context.Market.QuoteCurrency]
2020-07-20 03:49:20 +00:00
if quote.Available < volume*price {
log.Fatalf("quote balance not enough: %+v", quote)
}
2020-07-16 10:25:40 +00:00
quote.Available -= volume * price
trader.Context.Balances[trader.Context.Market.QuoteCurrency] = quote
base := trader.Context.Balances[trader.Context.Market.BaseCurrency]
base.Available += volume
trader.Context.Balances[trader.Context.Market.BaseCurrency] = base
2020-07-16 07:36:02 +00:00
} else {
fee = volume * 0.001
feeCurrency = "BTC"
2020-07-16 10:25:40 +00:00
base := trader.Context.Balances[trader.Context.Market.BaseCurrency]
if base.Available < volume {
log.Fatalf("base balance not enough: %+v", base)
}
2020-07-16 10:25:40 +00:00
base.Available -= volume
trader.Context.Balances[trader.Context.Market.BaseCurrency] = base
quote := trader.Context.Balances[trader.Context.Market.QuoteCurrency]
quote.Available += volume * price
trader.Context.Balances[trader.Context.Market.QuoteCurrency] = quote
2020-07-16 07:36:02 +00:00
}
trader.Context.Unlock()
2020-07-16 07:36:02 +00:00
trade := types.Trade{
ID: tradeID,
Price: price,
Quantity: volume,
2020-07-16 07:36:02 +00:00
Side: string(order.Side),
IsBuyer: order.Side == types.SideTypeBuy,
IsMaker: false,
Time: time.Unix(0, kline.EndTime*int64(time.Millisecond)),
Symbol: trader.Context.Symbol,
Fee: fee,
FeeCurrency: feeCurrency,
}
tradeID++
trader.ProfitAndLossCalculator.AddTrade(trade)
2020-07-20 03:49:20 +00:00
trader.doneOrders = append(trader.doneOrders, order)
2020-07-16 07:36:02 +00:00
}
2020-07-20 03:49:20 +00:00
// clear pending orders
trader.pendingOrders = nil
}
2020-07-16 07:36:02 +00:00
fmt.Print("\n")
report := trader.ProfitAndLossCalculator.Calculate()
report.Print()
log.Infof("wallet balance:")
for _, balance := range trader.Context.Balances {
log.Infof(" %s: %f", balance.Currency, balance.Available)
}
return done, nil
}
2020-07-10 13:34:39 +00:00
type Trader struct {
2020-07-13 05:25:48 +00:00
Notifier *SlackNotifier
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
2020-07-16 07:36:02 +00:00
ProfitAndLossCalculator *ProfitAndLossCalculator
2020-07-13 05:25:48 +00:00
}
2020-07-13 16:20:15 +00:00
func (trader *Trader) RunStrategy(ctx context.Context, strategy Strategy) (chan struct{}, error) {
symbol := trader.Context.Symbol
2020-07-13 05:25:48 +00:00
2020-07-13 16:20:15 +00:00
balances, err := trader.Exchange.QueryAccountBalances(ctx)
2020-07-13 05:25:48 +00:00
if err != nil {
2020-07-13 05:31:40 +00:00
return nil, err
2020-07-13 05:25:48 +00:00
}
2020-07-13 16:20:15 +00:00
trader.Context.Balances = balances
2020-07-13 16:38:52 +00:00
for _, balance := range balances {
if util.NotZero(balance.Available) {
log.Infof("[trader] balance %s %f", balance.Currency, balance.Available)
}
}
2020-07-13 16:20:15 +00:00
2020-07-16 07:36:02 +00:00
if err := strategy.Init(trader.Context, trader); err != nil {
2020-07-13 16:20:15 +00:00
return nil, err
}
stream, err := trader.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.OnNewStream(&stream.StandardPrivateStream); err != nil {
2020-07-13 16:20:15 +00:00
return nil, err
}
trader.reportTimer = time.AfterFunc(1*time.Second, func() {
trader.ReportPnL()
2020-07-13 05:25:48 +00:00
})
stream.OnTrade(func(trade *types.Trade) {
if trade.Symbol != symbol {
return
}
2020-07-13 16:20:15 +00:00
trader.ReportTrade(trade)
2020-07-16 07:36:02 +00:00
trader.ProfitAndLossCalculator.AddTrade(*trade)
2020-07-13 05:25:48 +00:00
2020-07-13 16:20:15 +00:00
if trader.reportTimer != nil {
trader.reportTimer.Stop()
2020-07-13 05:25:48 +00:00
}
2020-07-13 16:20:15 +00:00
trader.reportTimer = time.AfterFunc(5*time.Second, func() {
trader.ReportPnL()
2020-07-13 05:25:48 +00:00
})
})
stream.OnKLineEvent(func(e *binance.KLineEvent) {
2020-07-16 07:36:02 +00:00
trader.ProfitAndLossCalculator.SetCurrentPrice(e.KLine.GetClose())
2020-07-13 16:20:15 +00:00
trader.Context.SetCurrentPrice(e.KLine.GetClose())
})
stream.OnBalanceSnapshot(func(snapshot map[string]types.Balance) {
2020-07-13 16:20:15 +00:00
trader.Context.Lock()
defer trader.Context.Unlock()
2020-07-16 07:36:02 +00:00
for _, balance := range snapshot {
trader.Context.Balances[balance.Currency] = balance
2020-07-13 16:20:15 +00:00
}
})
// stream.OnOutboundAccountInfoEvent(func(e *binance.OutboundAccountInfoEvent) { })
2020-07-13 16:20:15 +00:00
stream.OnBalanceUpdateEvent(func(e *binance.BalanceUpdateEvent) {
trader.Context.Lock()
defer trader.Context.Unlock()
delta := util.MustParseFloat(e.Delta)
2020-07-16 07:36:02 +00:00
if balance, ok := trader.Context.Balances[e.Asset]; ok {
2020-07-13 16:20:15 +00:00
balance.Available += delta
trader.Context.Balances[e.Asset] = balance
}
2020-07-13 05:25:48 +00:00
})
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
}
2020-07-13 16:20:15 +00:00
func (trader *Trader) ReportTrade(trade *types.Trade) {
trader.Notifier.ReportTrade(trade)
2020-07-10 13:34:39 +00:00
}
2020-07-13 16:20:15 +00:00
func (trader *Trader) ReportPnL() {
2020-07-16 07:36:02 +00:00
report := trader.ProfitAndLossCalculator.Calculate()
2020-07-11 03:23:48 +00:00
report.Print()
2020-07-13 16:20:15 +00:00
trader.Notifier.ReportPnL(report)
2020-07-10 13:34:39 +00:00
}
2020-07-13 16:20:15 +00:00
func (trader *Trader) SubmitOrder(ctx context.Context, order *types.Order) {
trader.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
2020-07-13 16:20:15 +00:00
err := trader.Exchange.SubmitOrder(ctx, order)
2020-07-10 13:34:39 +00:00
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
}
}