//go:build !dnum package grid2 import ( "context" "os" "testing" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "git.qtrade.icu/lychiyu/qbtrade/pkg/backtest" "git.qtrade.icu/lychiyu/qbtrade/pkg/exchange" "git.qtrade.icu/lychiyu/qbtrade/pkg/qbtrade" "git.qtrade.icu/lychiyu/qbtrade/pkg/service" "git.qtrade.icu/lychiyu/qbtrade/pkg/types" "git.qtrade.icu/lychiyu/qbtrade/pkg/util" ) func RunBacktest(t *testing.T, strategy qbtrade.SingleExchangeStrategy) { // TEMPLATE {{{ start backtest const sqliteDbFile = "../../../data/qbtrade_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 := &qbtrade.Backtest{ StartTime: startTime, EndTime: &endTime, RecordTrades: false, FeeMode: qbtrade.BacktestFeeModeToken, Accounts: map[string]qbtrade.BacktestAccount{ backtestExchangeName: { MakerFeeRate: number(0.075 * 0.01), TakerFeeRate: number(0.075 * 0.01), Balances: qbtrade.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 := qbtrade.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 qbtrade.SetBackTesting(backtestService) defer qbtrade.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 := qbtrade.NewTrader(environ) if assert.NotNil(t, trader) { trader.DisableLogging() } userConfig := &qbtrade.Config{ Backtest: backtestConfig, ExchangeStrategies: []qbtrade.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 // }}} } func TestBacktestStrategy(t *testing.T) { if v, ok := util.GetEnvVarBool("TEST_BACKTEST"); !ok || !v { t.Skip("backtest flag is required") return } market := types.Market{ BaseCurrency: "BTC", QuoteCurrency: "USDT", TickSize: number(0.01), PricePrecision: 2, VolumePrecision: 8, } strategy := &Strategy{ logger: logrus.NewEntry(logrus.New()), Symbol: "BTCUSDT", Market: market, GridProfitStats: newGridProfitStats(market), UpperPrice: number(60_000), LowerPrice: number(28_000), GridNum: 100, QuoteInvestment: number(9000.0), } RunBacktest(t, strategy) }