bbgo_origin/pkg/backtest/stream.go

196 lines
4.6 KiB
Go
Raw Normal View History

package backtest
import (
"context"
2020-11-09 08:34:35 +00:00
"fmt"
2020-11-06 19:18:05 +00:00
log "github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/types"
)
type Stream struct {
types.StandardStream
exchange *Exchange
}
func (s *Stream) Connect(ctx context.Context) error {
2020-11-06 19:18:05 +00:00
log.Infof("collecting backtest configurations...")
loadedSymbols := map[string]struct{}{}
2020-11-07 12:34:34 +00:00
loadedIntervals := map[types.Interval]struct{}{
// 1m interval is required for the backtest matching engine
2020-11-10 06:18:04 +00:00
types.Interval1m: {},
types.Interval1d: {},
2020-11-07 12:34:34 +00:00
}
for _, sub := range s.Subscriptions {
loadedSymbols[sub.Symbol] = struct{}{}
switch sub.Channel {
case types.KLineChannel:
loadedIntervals[types.Interval(sub.Options.Interval)] = struct{}{}
default:
2020-11-09 08:34:35 +00:00
return fmt.Errorf("stream channel %s is not supported in backtest", sub.Channel)
}
}
var symbols []string
for symbol := range loadedSymbols {
symbols = append(symbols, symbol)
}
var intervals []types.Interval
for interval := range loadedIntervals {
intervals = append(intervals, interval)
}
2020-11-06 19:18:05 +00:00
log.Infof("used symbols: %v and intervals: %v", symbols, intervals)
2020-11-07 12:11:07 +00:00
go func() {
klineC, errC := s.exchange.srv.QueryKLinesCh(s.exchange.startTime, s.exchange, symbols, intervals)
for k := range klineC {
if k.Interval == types.Interval1m {
matching, ok := s.exchange.matchingBooks[k.Symbol]
if !ok {
log.Errorf("matching book of %s is not initialized", k.Symbol)
}
2020-11-10 06:18:04 +00:00
matching.processKLine(k)
}
2020-11-07 12:11:07 +00:00
s.EmitKLineClosed(k)
}
if err := <-errC; err != nil {
log.WithError(err).Error("backtest data feed error")
}
2020-11-07 12:34:34 +00:00
if err := s.Close(); err != nil {
2020-11-07 12:11:07 +00:00
log.WithError(err).Error("stream close error")
}
}()
return nil
}
func (s *Stream) Close() error {
2020-11-07 12:11:07 +00:00
close(s.exchange.doneC)
return nil
}
2020-10-18 04:25:08 +00:00
/*
func (trader *BackTestTrader) RunStrategy(ctx context.Context, strategy SingleExchangeStrategy) (chan struct{}, error) {
logrus.Infof("[regression] number of kline data: %d", len(trader.SourceKLines))
done := make(chan struct{})
defer close(done)
if err := strategy.OnLoad(trader.Context, trader); err != nil {
return nil, err
}
stream := &BackTestStream{}
if err := strategy.OnNewStream(stream); err != nil {
return nil, err
}
var tradeID int64 = 0
for _, kline := range trader.SourceKLines {
logrus.Debugf("kline %+v", kline)
fmt.Print(".")
stream.EmitKLineClosed(kline)
for _, order := range trader.pendingOrders {
switch order.Side {
case types.SideTypeBuy:
fmt.Print("B")
case types.SideTypeSell:
fmt.Print("S")
}
var price float64
if order.Type == types.OrderTypeLimit {
price = util.MustParseFloat(order.PriceString)
} else {
price = kline.GetClose()
}
volume := util.MustParseFloat(order.QuantityString)
fee := 0.0
feeCurrency := ""
trader.Context.Lock()
if order.Side == types.SideTypeBuy {
fee = price * volume * 0.001
feeCurrency = "USDT"
quote := trader.Context.Balances[trader.Context.Market.QuoteCurrency]
if quote.Available < volume*price {
logrus.Fatalf("quote balance not enough: %+v", quote)
}
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
} else {
fee = volume * 0.001
feeCurrency = "BTC"
base := trader.Context.Balances[trader.Context.Market.BaseCurrency]
if base.Available < volume {
logrus.Fatalf("base balance not enough: %+v", base)
}
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
}
trader.Context.Unlock()
trade := types.Trade{
ID: tradeID,
Price: price,
Quantity: volume,
Side: string(order.Side),
IsBuyer: order.Side == types.SideTypeBuy,
IsMaker: false,
Time: kline.EndTime,
Symbol: trader.Context.Symbol,
Fee: fee,
FeeCurrency: feeCurrency,
}
tradeID++
trader.AverageCostCalculator.AddTrade(trade)
trader.doneOrders = append(trader.doneOrders, order)
}
// clear pending orders
trader.pendingOrders = nil
}
fmt.Print("\n")
report := trader.AverageCostCalculator.Calculate()
report.Print()
logrus.Infof("wallet balance:")
for _, balance := range trader.Context.Balances {
logrus.Infof(" %s: %f", balance.Currency, balance.Available)
}
return done, nil
}
*/