2022-06-03 18:23:23 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-06-04 17:01:59 +00:00
|
|
|
"database/sql"
|
2022-12-03 03:02:36 +00:00
|
|
|
"os"
|
|
|
|
"strconv"
|
2022-06-03 18:23:23 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
2022-06-03 18:41:22 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2022-06-03 18:23:23 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
|
|
|
"github.com/c9s/bbgo/pkg/exchange"
|
|
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
|
|
)
|
|
|
|
|
2022-06-06 05:25:11 +00:00
|
|
|
func TestBacktestService_FindMissingTimeRanges_EmptyData(t *testing.T) {
|
2022-12-03 03:02:36 +00:00
|
|
|
if b, _ := strconv.ParseBool(os.Getenv("CI")); b {
|
|
|
|
t.Skip("skip test for CI")
|
|
|
|
}
|
|
|
|
|
2022-06-06 05:25:11 +00:00
|
|
|
db, err := prepareDB(t)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
dbx := sqlx.NewDb(db.DB, "sqlite3")
|
|
|
|
|
|
|
|
ex, err := exchange.NewPublic(types.ExchangeBinance)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
service := &BacktestService{DB: dbx}
|
|
|
|
|
|
|
|
symbol := "BTCUSDT"
|
|
|
|
now := time.Now()
|
|
|
|
startTime1 := now.AddDate(0, 0, -7).Truncate(time.Hour)
|
|
|
|
endTime1 := now.AddDate(0, 0, -6).Truncate(time.Hour)
|
2023-11-02 17:03:16 +00:00
|
|
|
timeRanges, err := service.findMissingTimeRanges(ctx, ex, symbol, types.Interval1h, startTime1, endTime1)
|
2022-06-06 05:25:11 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotEmpty(t, timeRanges)
|
|
|
|
}
|
|
|
|
|
2022-06-04 17:01:59 +00:00
|
|
|
func TestBacktestService_QueryExistingDataRange(t *testing.T) {
|
2022-12-03 03:02:36 +00:00
|
|
|
if b, _ := strconv.ParseBool(os.Getenv("CI")); b {
|
|
|
|
t.Skip("skip test for CI")
|
|
|
|
}
|
|
|
|
|
2022-06-04 17:01:59 +00:00
|
|
|
db, err := prepareDB(t)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
dbx := sqlx.NewDb(db.DB, "sqlite3")
|
|
|
|
|
|
|
|
ex, err := exchange.NewPublic(types.ExchangeBinance)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
service := &BacktestService{DB: dbx}
|
|
|
|
|
|
|
|
symbol := "BTCUSDT"
|
|
|
|
now := time.Now()
|
|
|
|
startTime1 := now.AddDate(0, 0, -7).Truncate(time.Hour)
|
|
|
|
endTime1 := now.AddDate(0, 0, -6).Truncate(time.Hour)
|
|
|
|
// empty range
|
2023-11-02 17:03:16 +00:00
|
|
|
t1, t2, err := service.queryExistingDataRange(ctx, ex, symbol, types.Interval1h, startTime1, endTime1)
|
2022-06-04 17:01:59 +00:00
|
|
|
assert.Error(t, sql.ErrNoRows, err)
|
|
|
|
assert.Nil(t, t1)
|
|
|
|
assert.Nil(t, t2)
|
|
|
|
}
|
|
|
|
|
2022-06-04 11:15:11 +00:00
|
|
|
func TestBacktestService_SyncPartial(t *testing.T) {
|
2022-12-03 03:02:36 +00:00
|
|
|
if b, _ := strconv.ParseBool(os.Getenv("CI")); b {
|
|
|
|
t.Skip("skip test for CI")
|
|
|
|
}
|
|
|
|
|
2022-06-04 11:15:11 +00:00
|
|
|
db, err := prepareDB(t)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
dbx := sqlx.NewDb(db.DB, "sqlite3")
|
|
|
|
|
|
|
|
ex, err := exchange.NewPublic(types.ExchangeBinance)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
service := &BacktestService{DB: dbx}
|
|
|
|
|
|
|
|
symbol := "BTCUSDT"
|
|
|
|
now := time.Now()
|
|
|
|
startTime1 := now.AddDate(0, 0, -7).Truncate(time.Hour)
|
|
|
|
endTime1 := now.AddDate(0, 0, -6).Truncate(time.Hour)
|
|
|
|
|
|
|
|
startTime2 := now.AddDate(0, 0, -5).Truncate(time.Hour)
|
|
|
|
endTime2 := now.AddDate(0, 0, -4).Truncate(time.Hour)
|
|
|
|
|
|
|
|
// kline query is exclusive
|
2023-11-02 17:03:16 +00:00
|
|
|
err = service.syncKLineByInterval(ctx, ex, symbol, types.Interval1h, startTime1.Add(-time.Second), endTime1.Add(time.Second))
|
2022-06-04 11:15:11 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2023-11-02 17:03:16 +00:00
|
|
|
err = service.syncKLineByInterval(ctx, ex, symbol, types.Interval1h, startTime2.Add(-time.Second), endTime2.Add(time.Second))
|
2022-06-04 11:15:11 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2023-11-02 17:03:16 +00:00
|
|
|
timeRanges, err := service.findMissingTimeRanges(ctx, ex, symbol, types.Interval1h, startTime1, endTime2)
|
2022-06-04 11:15:11 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotEmpty(t, timeRanges)
|
2022-06-06 17:21:27 +00:00
|
|
|
assert.Len(t, timeRanges, 1)
|
2022-06-04 11:15:11 +00:00
|
|
|
|
|
|
|
t.Run("fill missing time ranges", func(t *testing.T) {
|
2023-11-02 17:03:16 +00:00
|
|
|
err = service.syncPartial(ctx, ex, symbol, types.Interval1h, startTime1, endTime2)
|
2022-06-04 11:15:11 +00:00
|
|
|
assert.NoError(t, err, "sync partial should not return error")
|
|
|
|
|
2023-11-02 17:03:16 +00:00
|
|
|
timeRanges2, err := service.findMissingTimeRanges(ctx, ex, symbol, types.Interval1h, startTime1, endTime2)
|
2022-06-04 11:15:11 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Empty(t, timeRanges2)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBacktestService_FindMissingTimeRanges(t *testing.T) {
|
2022-12-03 03:02:36 +00:00
|
|
|
if b, _ := strconv.ParseBool(os.Getenv("CI")); b {
|
|
|
|
t.Skip("skip test for CI")
|
|
|
|
}
|
|
|
|
|
2022-06-03 18:23:23 +00:00
|
|
|
db, err := prepareDB(t)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer db.Close()
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
dbx := sqlx.NewDb(db.DB, "sqlite3")
|
|
|
|
|
|
|
|
ex, err := exchange.NewPublic(types.ExchangeBinance)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
service := &BacktestService{DB: dbx}
|
|
|
|
|
|
|
|
symbol := "BTCUSDT"
|
|
|
|
now := time.Now()
|
|
|
|
startTime1 := now.AddDate(0, 0, -6).Truncate(time.Hour)
|
|
|
|
endTime1 := now.AddDate(0, 0, -5).Truncate(time.Hour)
|
|
|
|
|
|
|
|
startTime2 := now.AddDate(0, 0, -4).Truncate(time.Hour)
|
|
|
|
endTime2 := now.AddDate(0, 0, -3).Truncate(time.Hour)
|
|
|
|
|
|
|
|
// kline query is exclusive
|
2023-11-02 17:03:16 +00:00
|
|
|
err = service.syncKLineByInterval(ctx, ex, symbol, types.Interval1h, startTime1.Add(-time.Second), endTime1.Add(time.Second))
|
2022-06-03 18:23:23 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2023-11-02 17:03:16 +00:00
|
|
|
err = service.syncKLineByInterval(ctx, ex, symbol, types.Interval1h, startTime2.Add(-time.Second), endTime2.Add(time.Second))
|
2022-06-03 18:23:23 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2023-11-02 17:03:16 +00:00
|
|
|
t1, t2, err := service.queryExistingDataRange(ctx, ex, symbol, types.Interval1h)
|
2022-06-03 18:23:23 +00:00
|
|
|
if assert.NoError(t, err) {
|
|
|
|
assert.Equal(t, startTime1, t1.Time(), "start time point should match")
|
|
|
|
assert.Equal(t, endTime2, t2.Time(), "end time point should match")
|
|
|
|
}
|
|
|
|
|
2023-11-02 17:03:16 +00:00
|
|
|
timeRanges, err := service.findMissingTimeRanges(ctx, ex, symbol, types.Interval1h, startTime1, endTime2)
|
2022-06-03 18:23:23 +00:00
|
|
|
if assert.NoError(t, err) {
|
|
|
|
assert.NotEmpty(t, timeRanges)
|
|
|
|
assert.Len(t, timeRanges, 1, "should find one missing time range")
|
|
|
|
t.Logf("found timeRanges: %+v", timeRanges)
|
2022-06-03 18:41:22 +00:00
|
|
|
|
|
|
|
log.SetLevel(log.DebugLevel)
|
|
|
|
|
|
|
|
for _, timeRange := range timeRanges {
|
2023-11-02 17:03:16 +00:00
|
|
|
err = service.syncKLineByInterval(ctx, ex, symbol, types.Interval1h, timeRange.Start.Add(time.Second), timeRange.End.Add(-time.Second))
|
2022-06-03 18:41:22 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
2023-11-02 17:03:16 +00:00
|
|
|
timeRanges, err = service.findMissingTimeRanges(ctx, ex, symbol, types.Interval1h, startTime1, endTime2)
|
2022-06-03 18:41:22 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Empty(t, timeRanges, "after partial sync, missing time ranges should be back-filled")
|
2022-06-03 18:23:23 +00:00
|
|
|
}
|
|
|
|
}
|