mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
Merge pull request #1021 from c9s/feature/grid2
grid2: add test case for aggregateOrderBaseFee
This commit is contained in:
commit
4c9403d919
156
pkg/strategy/grid2/backtest_test.go
Normal file
156
pkg/strategy/grid2/backtest_test.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
//go:build !dnum
|
||||
|
||||
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
|
||||
// }}}
|
||||
}
|
|
@ -128,6 +128,10 @@ func (s *Strategy) Validate() error {
|
|||
return fmt.Errorf("upperPrice (%s) should not be less than or equal to lowerPrice (%s)", s.UpperPrice.String(), s.LowerPrice.String())
|
||||
}
|
||||
|
||||
if s.GridNum == 0 {
|
||||
return fmt.Errorf("gridNum can not be zero")
|
||||
}
|
||||
|
||||
if s.FeeRate.IsZero() {
|
||||
s.FeeRate = fixedpoint.NewFromFloat(0.1 * 0.01) // 0.1%, 0.075% with BNB
|
||||
}
|
||||
|
@ -145,10 +149,6 @@ func (s *Strategy) Validate() error {
|
|||
}
|
||||
}
|
||||
|
||||
if s.GridNum == 0 {
|
||||
return fmt.Errorf("gridNum can not be zero")
|
||||
}
|
||||
|
||||
if err := s.QuantityOrAmount.Validate(); err != nil {
|
||||
if s.QuoteInvestment.IsZero() && s.BaseInvestment.IsZero() {
|
||||
return err
|
||||
|
@ -222,16 +222,16 @@ func collectTradeFee(trades []types.Trade) map[string]fixedpoint.Value {
|
|||
return fees
|
||||
}
|
||||
|
||||
func (s *Strategy) verifyOrderTrades(o types.Order, trades []types.Trade) bool {
|
||||
func aggregateTradesQuantity(trades []types.Trade) fixedpoint.Value {
|
||||
tq := fixedpoint.Zero
|
||||
for _, t := range trades {
|
||||
if t.Fee.IsZero() && t.FeeCurrency == "" {
|
||||
s.logger.Warnf("trade fee and feeCurrency is zero: %+v", t)
|
||||
return false
|
||||
}
|
||||
|
||||
tq = tq.Add(t.Quantity)
|
||||
}
|
||||
return tq
|
||||
}
|
||||
|
||||
func (s *Strategy) verifyOrderTrades(o types.Order, trades []types.Trade) bool {
|
||||
tq := aggregateTradesQuantity(trades)
|
||||
|
||||
if tq.Compare(o.Quantity) != 0 {
|
||||
s.logger.Warnf("order trades missing. expected: %f actual: %f",
|
||||
|
@ -363,7 +363,7 @@ func (s *Strategy) handleOrderFilled(o types.Order) {
|
|||
Tag: "grid",
|
||||
}
|
||||
|
||||
s.logger.Infof("SUBMIT ORDER: %s", orderForm.String())
|
||||
s.logger.Infof("SUBMIT GRID REVERSE ORDER: %s", orderForm.String())
|
||||
|
||||
if createdOrders, err := s.orderExecutor.SubmitOrders(context.Background(), orderForm); err != nil {
|
||||
s.logger.WithError(err).Errorf("can not submit arbitrage order")
|
||||
|
@ -372,13 +372,6 @@ func (s *Strategy) handleOrderFilled(o types.Order) {
|
|||
}
|
||||
}
|
||||
|
||||
type InvestmentBudget struct {
|
||||
baseInvestment fixedpoint.Value
|
||||
quoteInvestment fixedpoint.Value
|
||||
baseBalance fixedpoint.Value
|
||||
quoteBalance fixedpoint.Value
|
||||
}
|
||||
|
||||
func (s *Strategy) checkRequiredInvestmentByQuantity(baseBalance, quoteBalance, quantity, lastPrice fixedpoint.Value, pins []Pin) (requiredBase, requiredQuote fixedpoint.Value, err error) {
|
||||
// check more investment budget details
|
||||
requiredBase = fixedpoint.Zero
|
||||
|
@ -795,10 +788,18 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
|
|||
return err
|
||||
}
|
||||
|
||||
// debug info
|
||||
s.debugGridOrders(submitOrders, lastPrice)
|
||||
|
||||
for _, order := range createdOrders {
|
||||
s.logger.Info(order.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) debugGridOrders(submitOrders []types.SubmitOrder, lastPrice fixedpoint.Value) {
|
||||
s.logger.Infof("GRID ORDERS: [")
|
||||
for i, order := range submitOrders {
|
||||
|
||||
if i > 0 && lastPrice.Compare(order.Price) >= 0 && lastPrice.Compare(submitOrders[i-1].Price) <= 0 {
|
||||
s.logger.Infof(" - LAST PRICE: %f", lastPrice.Float64())
|
||||
}
|
||||
|
@ -806,12 +807,6 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
|
|||
s.logger.Info(" - ", order.String())
|
||||
}
|
||||
s.logger.Infof("] END OF GRID ORDERS")
|
||||
|
||||
for _, order := range createdOrders {
|
||||
s.logger.Info(order.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) generateGridOrders(totalQuote, totalBase, lastPrice fixedpoint.Value) ([]types.SubmitOrder, error) {
|
||||
|
@ -936,6 +931,25 @@ func (s *Strategy) getLastTradePrice(ctx context.Context, session *bbgo.Exchange
|
|||
return fixedpoint.Zero, fmt.Errorf("%s ticker price not found", s.Symbol)
|
||||
}
|
||||
|
||||
func calculateMinimalQuoteInvestment(market types.Market, lowerPrice, upperPrice fixedpoint.Value, gridNum int64) fixedpoint.Value {
|
||||
num := fixedpoint.NewFromInt(gridNum)
|
||||
minimalAmountLowerPrice := fixedpoint.Max(lowerPrice.Mul(market.MinQuantity), market.MinNotional)
|
||||
minimalAmountUpperPrice := fixedpoint.Max(upperPrice.Mul(market.MinQuantity), market.MinNotional)
|
||||
return fixedpoint.Max(minimalAmountLowerPrice, minimalAmountUpperPrice).Mul(num)
|
||||
}
|
||||
|
||||
func (s *Strategy) checkMinimalQuoteInvestment() error {
|
||||
minimalQuoteInvestment := calculateMinimalQuoteInvestment(s.Market, s.LowerPrice, s.UpperPrice, s.GridNum)
|
||||
if s.QuoteInvestment.Compare(minimalQuoteInvestment) <= 0 {
|
||||
return fmt.Errorf("need at least %f %s for quote investment, %f %s given",
|
||||
minimalQuoteInvestment.Float64(),
|
||||
s.Market.QuoteCurrency,
|
||||
s.QuoteInvestment.Float64(),
|
||||
s.Market.QuoteCurrency)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
instanceID := s.InstanceID()
|
||||
|
||||
|
@ -972,6 +986,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.Position.Reset()
|
||||
}
|
||||
|
||||
// we need to check the minimal quote investment here, because we need the market info
|
||||
if s.QuoteInvestment.Sign() > 0 {
|
||||
if err := s.checkMinimalQuoteInvestment(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.historicalTrades = bbgo.NewTradeStore()
|
||||
s.historicalTrades.EnablePrune = true
|
||||
s.historicalTrades.BindStream(session.UserDataStream)
|
||||
|
|
|
@ -4,18 +4,17 @@ package grid2
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"errors"
|
||||
"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) {
|
||||
|
@ -264,15 +263,18 @@ func newTestStrategy() *Strategy {
|
|||
TickSize: number(0.01),
|
||||
PricePrecision: 2,
|
||||
VolumePrecision: 8,
|
||||
MinNotional: number(10.0),
|
||||
MinQuantity: number(0.001),
|
||||
}
|
||||
|
||||
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 +335,163 @@ 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 TestStrategy_aggregateOrderBaseFeeRetry(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(nil, errors.New("api error"))
|
||||
|
||||
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 TestStrategy_checkMinimalQuoteInvestment(t *testing.T) {
|
||||
s := newTestStrategy()
|
||||
|
||||
t.Run("10 grids", func(t *testing.T) {
|
||||
// 10_000 * 0.001 = 10USDT
|
||||
// 20_000 * 0.001 = 20USDT
|
||||
// hence we should have at least: 20USDT * 10 grids
|
||||
s.QuoteInvestment = number(10_000)
|
||||
s.GridNum = 10
|
||||
minQuoteInvestment := calculateMinimalQuoteInvestment(s.Market, s.LowerPrice, s.UpperPrice, s.GridNum)
|
||||
assert.Equal(t, "200", minQuoteInvestment.String())
|
||||
|
||||
err := s.checkMinimalQuoteInvestment()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("1000 grids", func(t *testing.T) {
|
||||
s.QuoteInvestment = number(10_000)
|
||||
s.GridNum = 1000
|
||||
minQuoteInvestment := calculateMinimalQuoteInvestment(s.Market, s.LowerPrice, s.UpperPrice, s.GridNum)
|
||||
assert.Equal(t, "20000", minQuoteInvestment.String())
|
||||
|
||||
err := s.checkMinimalQuoteInvestment()
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "need at least 20000.000000 USDT for quote investment, 10000.000000 USDT given")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBacktestStrategy(t *testing.T) {
|
||||
market := types.Market{
|
||||
BaseCurrency: "BTC",
|
||||
|
@ -353,142 +512,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
|
||||
// }}}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
66
pkg/types/mocks/mock_exchange_order_query.go
Normal file
66
pkg/types/mocks/mock_exchange_order_query.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/c9s/bbgo/pkg/types (interfaces: ExchangeOrderQueryService)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
types "github.com/c9s/bbgo/pkg/types"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockExchangeOrderQueryService is a mock of ExchangeOrderQueryService interface.
|
||||
type MockExchangeOrderQueryService struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockExchangeOrderQueryServiceMockRecorder
|
||||
}
|
||||
|
||||
// MockExchangeOrderQueryServiceMockRecorder is the mock recorder for MockExchangeOrderQueryService.
|
||||
type MockExchangeOrderQueryServiceMockRecorder struct {
|
||||
mock *MockExchangeOrderQueryService
|
||||
}
|
||||
|
||||
// NewMockExchangeOrderQueryService creates a new mock instance.
|
||||
func NewMockExchangeOrderQueryService(ctrl *gomock.Controller) *MockExchangeOrderQueryService {
|
||||
mock := &MockExchangeOrderQueryService{ctrl: ctrl}
|
||||
mock.recorder = &MockExchangeOrderQueryServiceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockExchangeOrderQueryService) EXPECT() *MockExchangeOrderQueryServiceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// QueryOrder mocks base method.
|
||||
func (m *MockExchangeOrderQueryService) QueryOrder(arg0 context.Context, arg1 types.OrderQuery) (*types.Order, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "QueryOrder", arg0, arg1)
|
||||
ret0, _ := ret[0].(*types.Order)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// QueryOrder indicates an expected call of QueryOrder.
|
||||
func (mr *MockExchangeOrderQueryServiceMockRecorder) QueryOrder(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryOrder", reflect.TypeOf((*MockExchangeOrderQueryService)(nil).QueryOrder), arg0, arg1)
|
||||
}
|
||||
|
||||
// QueryOrderTrades mocks base method.
|
||||
func (m *MockExchangeOrderQueryService) QueryOrderTrades(arg0 context.Context, arg1 types.OrderQuery) ([]types.Trade, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "QueryOrderTrades", arg0, arg1)
|
||||
ret0, _ := ret[0].([]types.Trade)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// QueryOrderTrades indicates an expected call of QueryOrderTrades.
|
||||
func (mr *MockExchangeOrderQueryServiceMockRecorder) QueryOrderTrades(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryOrderTrades", reflect.TypeOf((*MockExchangeOrderQueryService)(nil).QueryOrderTrades), arg0, arg1)
|
||||
}
|
Loading…
Reference in New Issue
Block a user