diff --git a/config/bbgo.yaml b/config/bbgo.yaml index 7bc182700..0578a2a73 100644 --- a/config/bbgo.yaml +++ b/config/bbgo.yaml @@ -58,17 +58,9 @@ backtest: USDT: 5000.0 exchangeStrategies: -- on: binance +- on: max buyandhold: symbol: "BTCUSDT" interval: "1m" baseQuantity: 0.01 minDropPercentage: -0.02 -- on: max - xpuremaker: - symbol: MAXUSDT - numOrders: 2 - side: both - behindVolume: 1000.0 - priceTick: 0.01 - baseQuantity: 100.0 diff --git a/pkg/backtest/exchange.go b/pkg/backtest/exchange.go index 4584306e0..c4b54d0db 100644 --- a/pkg/backtest/exchange.go +++ b/pkg/backtest/exchange.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" + "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/exchange/binance" "github.com/c9s/bbgo/pkg/exchange/max" "github.com/c9s/bbgo/pkg/service" @@ -18,27 +19,55 @@ type Exchange struct { srv *service.BacktestService startTime time.Time + account *types.Account + config *bbgo.Backtest closedOrders []types.SubmitOrder openOrders []types.SubmitOrder + + stream *Stream } -func NewExchange(sourceExchange types.ExchangeName, srv *service.BacktestService, startTime time.Time) *Exchange { +func NewExchange(sourceExchange types.ExchangeName, srv *service.BacktestService, config *bbgo.Backtest) *Exchange { ex, err := newPublicExchange(sourceExchange) if err != nil { panic(err) } + if config == nil { + panic(errors.New("backtest config can not be nil")) + } + + startTime, err := config.ParseStartTime() + if err != nil { + panic(err) + } + + balances := config.Account.Balances.BalanceMap() + + account := &types.Account{ + MakerCommission: config.Account.MakerCommission, + TakerCommission: config.Account.TakerCommission, + AccountType: "SPOT", // currently not used + } + account.UpdateBalances(balances) + return &Exchange{ sourceExchange: sourceExchange, publicExchange: ex, srv: srv, + config: config, + account: account, startTime: startTime, } } func (e *Exchange) NewStream() types.Stream { - // TODO: return the stream and feed the data - return &Stream{} + if e.stream != nil { + panic("backtest stream is already allocated, please check if there are extra NewStream calls") + } + + e.stream = &Stream{exchange: e} + return e.stream } func (e Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (createdOrders types.OrderSlice, err error) { @@ -58,11 +87,11 @@ func (e Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error } func (e Exchange) QueryAccount(ctx context.Context) (*types.Account, error) { - panic("implement me") + return e.account, nil } -func (e Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) { - panic("implement me") +func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) { + return e.account.Balances(), nil } func (e Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) { diff --git a/pkg/backtest/stream.go b/pkg/backtest/stream.go index 84ccf647f..f403c2f66 100644 --- a/pkg/backtest/stream.go +++ b/pkg/backtest/stream.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" "github.com/c9s/bbgo/pkg/types" ) @@ -15,6 +16,8 @@ type Stream struct { } func (s *Stream) Connect(ctx context.Context) error { + log.Infof("collecting backtest configurations...") + loadedSymbols := map[string]struct{}{} loadedIntervals := map[types.Interval]struct{}{} for _, sub := range s.Subscriptions { @@ -39,6 +42,8 @@ func (s *Stream) Connect(ctx context.Context) error { intervals = append(intervals, interval) } + log.Infof("used symbols: %v and intervals: %v", symbols, intervals) + // TODO: we can sync before we connect /* if err := backtestService.Sync(ctx, exchange, symbol, startTime); err != nil { diff --git a/pkg/bbgo/config.go b/pkg/bbgo/config.go index 17437d89c..8c3400bca 100644 --- a/pkg/bbgo/config.go +++ b/pkg/bbgo/config.go @@ -4,11 +4,13 @@ import ( "encoding/json" "io/ioutil" "reflect" + "time" "github.com/pkg/errors" "gopkg.in/yaml.v3" "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" ) type PnLReporterConfig struct { @@ -57,6 +59,14 @@ type Backtest struct { Account BacktestAccount `json:"account" yaml:"account"` } +func (t Backtest) ParseStartTime() (time.Time, error) { + if len(t.StartTime) == 0 { + return time.Time{}, errors.New("backtest.startTime must be defined") + } + + return time.Parse("2006-01-02", t.StartTime) +} + type BacktestAccount struct { MakerCommission int `json:"makerCommission"` TakerCommission int `json:"takerCommission"` @@ -67,6 +77,18 @@ type BacktestAccount struct { type BacktestAccountBalanceMap map[string]fixedpoint.Value +func (m BacktestAccountBalanceMap) BalanceMap() types.BalanceMap { + balances := make(types.BalanceMap) + for currency, value := range m { + balances[currency] = types.Balance{ + Currency: currency, + Available: value.Float64(), + Locked: 0.0, + } + } + return balances +} + type Config struct { Imports []string `json:"imports" yaml:"imports"` diff --git a/pkg/cmd/backtest.go b/pkg/cmd/backtest.go index 4bcb855ef..cbfc613be 100644 --- a/pkg/cmd/backtest.go +++ b/pkg/cmd/backtest.go @@ -18,8 +18,7 @@ import ( func init() { BacktestCmd.Flags().String("exchange", "", "target exchange") - BacktestCmd.Flags().String("start", "", "start time") - BacktestCmd.Flags().Bool("backtest", true, "sync backtest data") + BacktestCmd.Flags().Bool("sync", true, "sync backtest data") BacktestCmd.Flags().String("config", "config/bbgo.yaml", "strategy config file") RootCmd.AddCommand(BacktestCmd) } @@ -61,27 +60,29 @@ var BacktestCmd = &cobra.Command{ return err } - // set default start time to the past 6 months - startTime := time.Now().AddDate(0, -6, 0) - - startTimeArg, err := cmd.Flags().GetString("start") - if err != nil { - return err + if userConfig.Backtest == nil { + return errors.New("backtest config is not defined") } - if len(startTimeArg) > 0 { - startTime, err = time.Parse("2006-01-02", startTimeArg) - if err != nil { - return err - } + // set default start time to the past 6 months + startTime := time.Now().AddDate(0, -6, 0) + if len(userConfig.Backtest.StartTime) == 0 { + userConfig.Backtest.StartTime = startTime.Format("2006-01-02") } backtestService := &service.BacktestService{DB: db} - exchange := backtest.NewExchange(exchangeName, backtestService, startTime) + exchange := backtest.NewExchange(exchangeName, backtestService, userConfig.Backtest) + environ := bbgo.NewEnvironment() environ.AddExchange(exchangeName.String(), exchange) + environ.Notifiability = bbgo.Notifiability{ + SymbolChannelRouter: bbgo.NewPatternChannelRouter(nil), + SessionChannelRouter: bbgo.NewPatternChannelRouter(nil), + ObjectChannelRouter: bbgo.NewObjectChannelRouter(), + } + trader := bbgo.NewTrader(environ) if userConfig.RiskControls != nil { trader.SetRiskControls(userConfig.RiskControls) @@ -96,7 +97,7 @@ var BacktestCmd = &cobra.Command{ log.Warnf("backtest does not support CrossExchangeStrategy, strategies won't be added.") } - if err := trader.Run(ctx) ; err != nil { + if err := trader.Run(ctx); err != nil { return err } diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 5921de59f..95c742157 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -262,8 +262,8 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) { } a := &types.Account{ - MakerCommission: account.MakerCommission, - TakerCommission: account.TakerCommission, + MakerCommission: int(account.MakerCommission), + TakerCommission: int(account.TakerCommission), } a.UpdateBalances(balances) return a, nil @@ -537,4 +537,3 @@ func (e *Exchange) BatchQueryKLines(ctx context.Context, symbol string, interval return allKLines, nil } - diff --git a/pkg/types/account.go b/pkg/types/account.go index cbbbbbcc2..a68a5cc6b 100644 --- a/pkg/types/account.go +++ b/pkg/types/account.go @@ -19,9 +19,9 @@ type BalanceMap map[string]Balance type Account struct { sync.Mutex - MakerCommission int64 - TakerCommission int64 - AccountType string + MakerCommission int `json:"makerCommission"` + TakerCommission int `json:"takerCommission"` + AccountType string `json:"accountType"` balances BalanceMap }