2020-10-16 02:14:36 +00:00
|
|
|
package bbgo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
2020-10-17 16:06:08 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2020-10-16 02:14:36 +00:00
|
|
|
"github.com/spf13/viper"
|
|
|
|
|
|
|
|
"github.com/c9s/bbgo/cmd/cmdutil"
|
|
|
|
"github.com/c9s/bbgo/pkg/service"
|
2020-10-18 04:23:00 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/store"
|
2020-10-16 02:14:36 +00:00
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Environment presents the real exchange data layer
|
|
|
|
type Environment struct {
|
|
|
|
TradeService *service.TradeService
|
|
|
|
TradeSync *service.TradeSync
|
|
|
|
|
|
|
|
sessions map[string]*ExchangeSession
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewDefaultEnvironment(db *sqlx.DB) *Environment {
|
|
|
|
environment := NewEnvironment(db)
|
|
|
|
|
|
|
|
for _, n := range SupportedExchanges {
|
|
|
|
if viper.IsSet(string(n) + "-api-key") {
|
|
|
|
exchange, err := cmdutil.NewExchange(n)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
environment.AddExchange(string(n), exchange)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return environment
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewEnvironment(db *sqlx.DB) *Environment {
|
|
|
|
tradeService := &service.TradeService{DB: db}
|
|
|
|
return &Environment{
|
|
|
|
TradeService: tradeService,
|
|
|
|
TradeSync: &service.TradeSync{
|
|
|
|
Service: tradeService,
|
|
|
|
},
|
|
|
|
sessions: make(map[string]*ExchangeSession),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (environ *Environment) AddExchange(name string, exchange types.Exchange) (session *ExchangeSession) {
|
2020-10-17 15:51:44 +00:00
|
|
|
session = NewExchangeSession(name, exchange)
|
2020-10-16 02:14:36 +00:00
|
|
|
environ.sessions[name] = session
|
|
|
|
return session
|
|
|
|
}
|
|
|
|
|
|
|
|
func (environ *Environment) Init(ctx context.Context) (err error) {
|
|
|
|
startTime := time.Now().AddDate(0, 0, -7) // sync from 7 days ago
|
|
|
|
|
|
|
|
for _, session := range environ.sessions {
|
|
|
|
loadedSymbols := make(map[string]struct{})
|
|
|
|
for _, sub := range session.Subscriptions {
|
|
|
|
loadedSymbols[sub.Symbol] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
markets, err := session.Exchange.QueryMarkets(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-18 04:30:13 +00:00
|
|
|
session.markets = markets
|
2020-10-16 02:14:36 +00:00
|
|
|
|
|
|
|
for symbol := range loadedSymbols {
|
2020-10-17 16:06:08 +00:00
|
|
|
log.Infof("syncing trades from %s for symbol %s...", session.Exchange.Name(), symbol)
|
2020-10-16 02:14:36 +00:00
|
|
|
if err := environ.TradeSync.Sync(ctx, session.Exchange, symbol, startTime); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var trades []types.Trade
|
|
|
|
|
|
|
|
tradingFeeCurrency := session.Exchange.PlatformFeeCurrency()
|
|
|
|
if strings.HasPrefix(symbol, tradingFeeCurrency) {
|
|
|
|
trades, err = environ.TradeService.QueryForTradingFeeCurrency(symbol, tradingFeeCurrency)
|
|
|
|
} else {
|
|
|
|
trades, err = environ.TradeService.Query(symbol)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-17 16:06:08 +00:00
|
|
|
log.Infof("symbol %s: %d trades loaded", symbol, len(trades))
|
2020-10-16 02:14:36 +00:00
|
|
|
session.Trades[symbol] = trades
|
|
|
|
|
|
|
|
currentPrice, err := session.Exchange.QueryAveragePrice(ctx, symbol)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-18 04:29:38 +00:00
|
|
|
session.lastPrices[symbol] = currentPrice
|
2020-10-17 16:06:08 +00:00
|
|
|
|
2020-10-18 04:32:43 +00:00
|
|
|
session.klineStores[symbol] = store.NewMarketDataStore(symbol)
|
2020-10-16 02:14:36 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 16:06:08 +00:00
|
|
|
log.Infof("querying balances...")
|
2020-10-16 02:14:36 +00:00
|
|
|
balances, err := session.Exchange.QueryAccountBalances(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stream := session.Exchange.NewStream()
|
|
|
|
|
2020-10-18 03:30:37 +00:00
|
|
|
account := &types.Account{}
|
|
|
|
account.UpdateBalances(balances)
|
2020-10-17 16:06:08 +00:00
|
|
|
account.BindStream(stream)
|
|
|
|
session.Account = account
|
2020-10-16 02:14:36 +00:00
|
|
|
|
|
|
|
// update last prices
|
2020-10-17 16:06:08 +00:00
|
|
|
stream.OnKLineClosed(func(kline types.KLine) {
|
2020-10-18 04:29:38 +00:00
|
|
|
session.lastPrices[kline.Symbol] = kline.Close
|
2020-10-18 04:32:43 +00:00
|
|
|
session.klineStores[kline.Symbol].AddKLine(kline)
|
2020-10-16 02:14:36 +00:00
|
|
|
})
|
|
|
|
|
2020-10-17 16:06:08 +00:00
|
|
|
stream.OnTrade(func(trade *types.Trade) {
|
2020-10-16 02:14:36 +00:00
|
|
|
// append trades
|
|
|
|
session.Trades[trade.Symbol] = append(session.Trades[trade.Symbol], *trade)
|
|
|
|
|
|
|
|
if err := environ.TradeService.Insert(*trade); err != nil {
|
2020-10-17 16:06:08 +00:00
|
|
|
log.WithError(err).Errorf("trade insert error: %+v", *trade)
|
2020-10-16 02:14:36 +00:00
|
|
|
}
|
|
|
|
})
|
2020-10-17 16:06:08 +00:00
|
|
|
|
|
|
|
session.Stream = stream
|
2020-10-16 02:14:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (environ *Environment) Connect(ctx context.Context) error {
|
|
|
|
for _, session := range environ.sessions {
|
2020-10-17 16:06:08 +00:00
|
|
|
log.Infof("connecting session %s...", session.Name)
|
|
|
|
|
2020-10-16 02:14:36 +00:00
|
|
|
for _, s := range session.Subscriptions {
|
2020-10-17 16:06:08 +00:00
|
|
|
log.Infof("subscribing %s %s %v", s.Symbol, s.Channel, s.Options)
|
2020-10-16 02:14:36 +00:00
|
|
|
session.Stream.Subscribe(s.Channel, s.Symbol, s.Options)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := session.Stream.Connect(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|