From 3d0cfd16b536cebd21ac460761bae4ee3e0399b5 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 6 Dec 2022 15:21:22 +0800 Subject: [PATCH] grid2: add test case for aggregateOrderBaseFee --- pkg/strategy/grid2/backtest_test.go | 154 +++++++++++++++++++ pkg/strategy/grid2/strategy_test.go | 220 +++++++++------------------- pkg/types/exchange.go | 1 + 3 files changed, 226 insertions(+), 149 deletions(-) create mode 100644 pkg/strategy/grid2/backtest_test.go diff --git a/pkg/strategy/grid2/backtest_test.go b/pkg/strategy/grid2/backtest_test.go new file mode 100644 index 000000000..1d6aa2307 --- /dev/null +++ b/pkg/strategy/grid2/backtest_test.go @@ -0,0 +1,154 @@ +package grid2 + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/backtest" + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/exchange" + "github.com/c9s/bbgo/pkg/service" + "github.com/c9s/bbgo/pkg/types" +) + +func RunBacktest(t *testing.T, strategy bbgo.SingleExchangeStrategy) { + // TEMPLATE {{{ start backtest + const sqliteDbFile = "../../../data/bbgo_test.sqlite3" + const backtestExchangeName = "binance" + const backtestStartTime = "2022-06-01" + const backtestEndTime = "2022-06-30" + + startTime, err := types.ParseLooseFormatTime(backtestStartTime) + assert.NoError(t, err) + + endTime, err := types.ParseLooseFormatTime(backtestEndTime) + assert.NoError(t, err) + + backtestConfig := &bbgo.Backtest{ + StartTime: startTime, + EndTime: &endTime, + RecordTrades: false, + FeeMode: bbgo.BacktestFeeModeToken, + Accounts: map[string]bbgo.BacktestAccount{ + backtestExchangeName: { + MakerFeeRate: number(0.075 * 0.01), + TakerFeeRate: number(0.075 * 0.01), + Balances: bbgo.BacktestAccountBalanceMap{ + "USDT": number(10_000.0), + "BTC": number(1.0), + }, + }, + }, + Symbols: []string{"BTCUSDT"}, + Sessions: []string{backtestExchangeName}, + SyncSecKLines: false, + } + + t.Logf("backtestConfig: %+v", backtestConfig) + + ctx := context.Background() + environ := bbgo.NewEnvironment() + environ.SetStartTime(startTime.Time()) + + info, err := os.Stat(sqliteDbFile) + assert.NoError(t, err) + t.Logf("sqlite: %+v", info) + + err = environ.ConfigureDatabaseDriver(ctx, "sqlite3", sqliteDbFile) + if !assert.NoError(t, err) { + return + } + + backtestService := &service.BacktestService{DB: environ.DatabaseService.DB} + defer func() { + err := environ.DatabaseService.DB.Close() + assert.NoError(t, err) + }() + + environ.BacktestService = backtestService + bbgo.SetBackTesting(backtestService) + defer bbgo.SetBackTesting(nil) + + exName, err := types.ValidExchangeName(backtestExchangeName) + if !assert.NoError(t, err) { + return + } + + t.Logf("using exchange source: %s", exName) + + publicExchange, err := exchange.NewPublic(exName) + if !assert.NoError(t, err) { + return + } + + backtestExchange, err := backtest.NewExchange(exName, publicExchange, backtestService, backtestConfig) + if !assert.NoError(t, err) { + return + } + + session := environ.AddExchange(backtestExchangeName, backtestExchange) + assert.NotNil(t, session) + + err = environ.Init(ctx) + assert.NoError(t, err) + + for _, ses := range environ.Sessions() { + userDataStream := ses.UserDataStream.(types.StandardStreamEmitter) + backtestEx := ses.Exchange.(*backtest.Exchange) + backtestEx.MarketDataStream = ses.MarketDataStream.(types.StandardStreamEmitter) + backtestEx.BindUserData(userDataStream) + } + + trader := bbgo.NewTrader(environ) + if assert.NotNil(t, trader) { + trader.DisableLogging() + } + + userConfig := &bbgo.Config{ + Backtest: backtestConfig, + ExchangeStrategies: []bbgo.ExchangeStrategyMount{ + { + Mounts: []string{backtestExchangeName}, + Strategy: strategy, + }, + }, + } + + err = trader.Configure(userConfig) + assert.NoError(t, err) + + err = trader.Run(ctx) + assert.NoError(t, err) + + allKLineIntervals, requiredInterval, backTestIntervals := backtest.CollectSubscriptionIntervals(environ) + t.Logf("requiredInterval: %s backTestIntervals: %v", requiredInterval, backTestIntervals) + + _ = allKLineIntervals + exchangeSources, err := backtest.InitializeExchangeSources(environ.Sessions(), startTime.Time(), endTime.Time(), requiredInterval, backTestIntervals...) + if !assert.NoError(t, err) { + return + } + + doneC := make(chan struct{}) + go func() { + count := 0 + exSource := exchangeSources[0] + for k := range exSource.C { + exSource.Exchange.ConsumeKLine(k, requiredInterval) + count++ + } + + err = exSource.Exchange.CloseMarketData() + assert.NoError(t, err) + + assert.Greater(t, count, 0, "kLines count must be greater than 0, please check your backtest date range and symbol settings") + + close(doneC) + }() + + <-doneC + // }}} +} diff --git a/pkg/strategy/grid2/strategy_test.go b/pkg/strategy/grid2/strategy_test.go index 3912e98d5..4ea85258d 100644 --- a/pkg/strategy/grid2/strategy_test.go +++ b/pkg/strategy/grid2/strategy_test.go @@ -4,18 +4,16 @@ package grid2 import ( "context" - "os" "testing" + "github.com/golang/mock/gomock" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" - "github.com/c9s/bbgo/pkg/backtest" "github.com/c9s/bbgo/pkg/bbgo" - "github.com/c9s/bbgo/pkg/exchange" "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/service" "github.com/c9s/bbgo/pkg/types" + "github.com/c9s/bbgo/pkg/types/mocks" ) func TestStrategy_checkRequiredInvestmentByQuantity(t *testing.T) { @@ -267,12 +265,13 @@ func newTestStrategy() *Strategy { } s := &Strategy{ - logger: logrus.NewEntry(logrus.New()), - Market: market, - GridProfitStats: newGridProfitStats(market), - UpperPrice: number(20_000), - LowerPrice: number(10_000), - GridNum: 10, + logger: logrus.NewEntry(logrus.New()), + Market: market, + GridProfitStats: newGridProfitStats(market), + UpperPrice: number(20_000), + LowerPrice: number(10_000), + GridNum: 10, + historicalTrades: bbgo.NewTradeStore(), // QuoteInvestment: number(9000.0), } @@ -333,6 +332,68 @@ func TestStrategy_calculateProfit(t *testing.T) { }) } +func TestStrategy_aggregateOrderBaseFee(t *testing.T) { + s := newTestStrategy() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockService := mocks.NewMockExchangeOrderQueryService(mockCtrl) + s.orderQueryService = mockService + + ctx := context.Background() + mockService.EXPECT().QueryOrderTrades(ctx, types.OrderQuery{ + Symbol: "BTCUSDT", + OrderID: "3", + }).Return([]types.Trade{ + { + ID: 1, + OrderID: 3, + Exchange: "binance", + Price: number(20000.0), + Quantity: number(0.2), + Symbol: "BTCUSDT", + Side: types.SideTypeBuy, + IsBuyer: true, + FeeCurrency: "BTC", + Fee: number(0.2 * 0.01), + }, + { + ID: 1, + OrderID: 3, + Exchange: "binance", + Price: number(20000.0), + Quantity: number(0.8), + Symbol: "BTCUSDT", + Side: types.SideTypeBuy, + IsBuyer: true, + FeeCurrency: "BTC", + Fee: number(0.8 * 0.01), + }, + }, nil) + + baseFee := s.aggregateOrderBaseFee(types.Order{ + SubmitOrder: types.SubmitOrder{ + Symbol: "BTCUSDT", + Side: types.SideTypeBuy, + Type: types.OrderTypeLimit, + Quantity: number(1.0), + Price: number(20000.0), + AveragePrice: number(0), + StopPrice: number(0), + Market: types.Market{}, + TimeInForce: types.TimeInForceGTC, + }, + Exchange: "binance", + GID: 1, + OrderID: 3, + Status: types.OrderStatusFilled, + ExecutedQuantity: number(1.0), + IsWorking: false, + }) + assert.Equal(t, "0.01", baseFee.String()) +} + func TestBacktestStrategy(t *testing.T) { market := types.Market{ BaseCurrency: "BTC", @@ -353,142 +414,3 @@ func TestBacktestStrategy(t *testing.T) { } RunBacktest(t, strategy) } - -func RunBacktest(t *testing.T, strategy bbgo.SingleExchangeStrategy) { - // TEMPLATE {{{ start backtest - const sqliteDbFile = "../../../data/bbgo_test.sqlite3" - const backtestExchangeName = "binance" - const backtestStartTime = "2022-06-01" - const backtestEndTime = "2022-06-30" - - startTime, err := types.ParseLooseFormatTime(backtestStartTime) - assert.NoError(t, err) - - endTime, err := types.ParseLooseFormatTime(backtestEndTime) - assert.NoError(t, err) - - backtestConfig := &bbgo.Backtest{ - StartTime: startTime, - EndTime: &endTime, - RecordTrades: false, - FeeMode: bbgo.BacktestFeeModeToken, - Accounts: map[string]bbgo.BacktestAccount{ - backtestExchangeName: { - MakerFeeRate: number(0.075 * 0.01), - TakerFeeRate: number(0.075 * 0.01), - Balances: bbgo.BacktestAccountBalanceMap{ - "USDT": number(10_000.0), - "BTC": number(1.0), - }, - }, - }, - Symbols: []string{"BTCUSDT"}, - Sessions: []string{backtestExchangeName}, - SyncSecKLines: false, - } - - t.Logf("backtestConfig: %+v", backtestConfig) - - ctx := context.Background() - environ := bbgo.NewEnvironment() - environ.SetStartTime(startTime.Time()) - - info, err := os.Stat(sqliteDbFile) - assert.NoError(t, err) - t.Logf("sqlite: %+v", info) - - err = environ.ConfigureDatabaseDriver(ctx, "sqlite3", sqliteDbFile) - if !assert.NoError(t, err) { - return - } - - backtestService := &service.BacktestService{DB: environ.DatabaseService.DB} - defer func() { - err := environ.DatabaseService.DB.Close() - assert.NoError(t, err) - }() - - environ.BacktestService = backtestService - bbgo.SetBackTesting(backtestService) - defer bbgo.SetBackTesting(nil) - - exName, err := types.ValidExchangeName(backtestExchangeName) - if !assert.NoError(t, err) { - return - } - - t.Logf("using exchange source: %s", exName) - - publicExchange, err := exchange.NewPublic(exName) - if !assert.NoError(t, err) { - return - } - - backtestExchange, err := backtest.NewExchange(exName, publicExchange, backtestService, backtestConfig) - if !assert.NoError(t, err) { - return - } - - session := environ.AddExchange(backtestExchangeName, backtestExchange) - assert.NotNil(t, session) - - err = environ.Init(ctx) - assert.NoError(t, err) - - for _, ses := range environ.Sessions() { - userDataStream := ses.UserDataStream.(types.StandardStreamEmitter) - backtestEx := ses.Exchange.(*backtest.Exchange) - backtestEx.MarketDataStream = ses.MarketDataStream.(types.StandardStreamEmitter) - backtestEx.BindUserData(userDataStream) - } - - trader := bbgo.NewTrader(environ) - if assert.NotNil(t, trader) { - trader.DisableLogging() - } - - userConfig := &bbgo.Config{ - Backtest: backtestConfig, - ExchangeStrategies: []bbgo.ExchangeStrategyMount{ - { - Mounts: []string{backtestExchangeName}, - Strategy: strategy, - }, - }, - } - - err = trader.Configure(userConfig) - assert.NoError(t, err) - - err = trader.Run(ctx) - assert.NoError(t, err) - - allKLineIntervals, requiredInterval, backTestIntervals := backtest.CollectSubscriptionIntervals(environ) - t.Logf("requiredInterval: %s backTestIntervals: %v", requiredInterval, backTestIntervals) - - _ = allKLineIntervals - exchangeSources, err := backtest.InitializeExchangeSources(environ.Sessions(), startTime.Time(), endTime.Time(), requiredInterval, backTestIntervals...) - if !assert.NoError(t, err) { - return - } - - doneC := make(chan struct{}) - go func() { - count := 0 - exSource := exchangeSources[0] - for k := range exSource.C { - exSource.Exchange.ConsumeKLine(k, requiredInterval) - count++ - } - - err = exSource.Exchange.CloseMarketData() - assert.NoError(t, err) - - assert.Greater(t, count, 0, "kLines count must be greater than 0, please check your backtest date range and symbol settings") - - close(doneC) - }() - - <-doneC - // }}} -} diff --git a/pkg/types/exchange.go b/pkg/types/exchange.go index bae287c79..cab368a42 100644 --- a/pkg/types/exchange.go +++ b/pkg/types/exchange.go @@ -82,6 +82,7 @@ type Exchange interface { } // ExchangeOrderQueryService provides an interface for querying the order status via order ID or client order ID +//go:generate mockgen -destination=mocks/mock_exchange_order_query.go -package=mocks . ExchangeOrderQueryService type ExchangeOrderQueryService interface { QueryOrder(ctx context.Context, q OrderQuery) (*Order, error) QueryOrderTrades(ctx context.Context, q OrderQuery) ([]Trade, error)