code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/candles_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package sqlstore_test
    17  
    18  import (
    19  	"context"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/datanode/candlesv2"
    26  	"code.vegaprotocol.io/vega/datanode/entities"
    27  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    28  	types "code.vegaprotocol.io/vega/protos/vega"
    29  
    30  	"github.com/shopspring/decimal"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  const (
    36  	StartTime            = 1649116800
    37  	totalBlocks          = 1000
    38  	tradesPerBlock       = 5
    39  	startPrice           = 1
    40  	size                 = 10
    41  	priceIncrement       = 1
    42  	blockIntervalSeconds = 10
    43  	blockIntervalDur     = time.Duration(blockIntervalSeconds) * time.Second
    44  )
    45  
    46  func TestGetExistingCandles(t *testing.T) {
    47  	ctx := tempTransaction(t)
    48  
    49  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
    50  
    51  	candles, err := candleStore.GetCandlesForMarket(ctx, testMarket)
    52  	if err != nil {
    53  		t.Fatalf("failed to get candles for market:%s", err)
    54  	}
    55  
    56  	defaultCandles := "block,1 minute,5 minutes,15 minutes,30 minutes,1 hour,4 hours,6 hours,8 hours,12 hours,1 day,7 days"
    57  	intervals := strings.Split(defaultCandles, ",")
    58  	assert.Equal(t, len(intervals), len(candles))
    59  
    60  	for _, interval := range intervals {
    61  		candleID := candles[interval]
    62  		exists, _ := candleStore.CandleExists(ctx, candleID)
    63  		assert.True(t, exists)
    64  	}
    65  }
    66  
    67  func TestCandlesPagination(t *testing.T) {
    68  	ctx := tempTransaction(t)
    69  
    70  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
    71  
    72  	tradeStore := sqlstore.NewTrades(connectionSource)
    73  
    74  	startTime := time.Unix(StartTime, 0)
    75  	insertCandlesTestData(t, ctx, tradeStore, startTime, totalBlocks, tradesPerBlock, startPrice, priceIncrement, size, blockIntervalDur)
    76  
    77  	_, candleID, _ := candleStore.GetCandleIDForIntervalAndMarket(ctx, "1 Minute", testMarket)
    78  	first := int32(10)
    79  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
    80  	require.NoError(t, err)
    81  
    82  	candles, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, nil,
    83  		nil, pagination)
    84  	if err != nil {
    85  		t.Fatalf("failed to get candles with pagination:%s", err)
    86  	}
    87  
    88  	assert.Equal(t, 10, len(candles))
    89  	lastCandle := candles[9]
    90  
    91  	first = int32(5)
    92  	after := candles[8].Cursor().Encode()
    93  
    94  	pagination, _ = entities.NewCursorPagination(&first, &after, nil, nil, false)
    95  
    96  	candles, _, err = candleStore.GetCandleDataForTimeSpan(ctx, candleID, nil,
    97  		nil, pagination)
    98  	if err != nil {
    99  		t.Fatalf("failed to get candles with pagination:%s", err)
   100  	}
   101  
   102  	assert.Equal(t, 5, len(candles))
   103  	assert.Equal(t, lastCandle, candles[0])
   104  }
   105  
   106  func TestNotional(t *testing.T) {
   107  	ctx := tempTransaction(t)
   108  
   109  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
   110  	tradeStore := sqlstore.NewTrades(connectionSource)
   111  	bs := sqlstore.NewBlocks(connectionSource)
   112  
   113  	startTime := time.Unix(StartTime, 0)
   114  	block := addTestBlockForTime(t, ctx, bs, startTime)
   115  
   116  	// Total notional here will be 1*10 + 2*15 = 40
   117  	insertTestTrade(t, ctx, tradeStore, 1, 10, block, 0)
   118  	insertTestTrade(t, ctx, tradeStore, 2, 15, block, 3)
   119  
   120  	nextTime := time.Unix(StartTime, 0).Add(10 * time.Minute)
   121  	block = addTestBlockForTime(t, ctx, bs, nextTime)
   122  	// Total notional here will be 3*20 + 4*25 = 160
   123  	insertTestTrade(t, ctx, tradeStore, 3, 20, block, 0)
   124  	insertTestTrade(t, ctx, tradeStore, 4, 25, block, 5)
   125  
   126  	_, candleID, err := candleStore.GetCandleIDForIntervalAndMarket(ctx, "1 Minute", testMarket)
   127  	if err != nil {
   128  		t.Fatalf("getting existing candleDescriptor id:%s", err)
   129  	}
   130  
   131  	pagination, _ := entities.NewCursorPagination(nil, nil, nil, nil, false)
   132  	candles, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime,
   133  		nil, pagination)
   134  	if err != nil {
   135  		t.Fatalf("failed to get candles:%s", err)
   136  	}
   137  
   138  	assert.Equal(t, uint64(40), candles[0].Notional)
   139  	assert.Equal(t, uint64(160), candles[len(candles)-1].Notional)
   140  }
   141  
   142  func TestCandlesGetForEmptyInterval(t *testing.T) {
   143  	ctx := tempTransaction(t)
   144  
   145  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
   146  	tradeStore := sqlstore.NewTrades(connectionSource)
   147  	bs := sqlstore.NewBlocks(connectionSource)
   148  
   149  	startTime := time.Unix(StartTime, 0)
   150  	block := addTestBlockForTime(t, ctx, bs, startTime)
   151  
   152  	insertTestTrade(t, ctx, tradeStore, 1, 10, block, 0)
   153  	insertTestTrade(t, ctx, tradeStore, 2, 10, block, 3)
   154  
   155  	nextTime := time.Unix(StartTime, 0).Add(10 * time.Minute)
   156  	block = addTestBlockForTime(t, ctx, bs, nextTime)
   157  	insertTestTrade(t, ctx, tradeStore, 3, 20, block, 0)
   158  	insertTestTrade(t, ctx, tradeStore, 4, 20, block, 5)
   159  
   160  	_, candleID, err := candleStore.GetCandleIDForIntervalAndMarket(ctx, "1 Minute", testMarket)
   161  	if err != nil {
   162  		t.Fatalf("getting existing candleDescriptor id:%s", err)
   163  	}
   164  
   165  	pagination, _ := entities.NewCursorPagination(nil, nil, nil, nil, false)
   166  	candles, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime,
   167  		nil, pagination)
   168  	if err != nil {
   169  		t.Fatalf("failed to get candles:%s", err)
   170  	}
   171  
   172  	assert.Equal(t, 11, len(candles)) // 11 now because we are filling in the gaps
   173  
   174  	firstCandle := createCandle(startTime,
   175  		startTime.Add(3*time.Microsecond), 1, 2, 2, 1, 20, 30)
   176  	assert.Equal(t, firstCandle, candles[0])
   177  	// Fill gaps should carry over the last candle's prices
   178  	gapPeriodStart := firstCandle.PeriodStart.Add(1 * time.Minute)
   179  
   180  	assert.Equal(t, gapPeriodStart, candles[1].PeriodStart)
   181  	assert.True(t, decimal.Zero.Equal(candles[1].Open))
   182  	assert.True(t, decimal.Zero.Equal(candles[1].High))
   183  	assert.True(t, decimal.Zero.Equal(candles[1].Low))
   184  	assert.True(t, decimal.Zero.Equal(candles[1].Close))
   185  	assert.Equal(t, uint64(0), candles[1].Volume)
   186  	assert.Equal(t, uint64(0), candles[1].Notional)
   187  	assert.True(t, firstCandle.LastUpdateInPeriod.Equal(candles[1].LastUpdateInPeriod))
   188  
   189  	secondCandle := createCandle(startTime.Add(10*time.Minute),
   190  		startTime.Add(10*time.Minute).Add(5*time.Microsecond), 3, 4, 4, 3, 40, 140)
   191  	assert.Equal(t, secondCandle, candles[len(candles)-1])
   192  }
   193  
   194  func TestCandlesGetLatest(t *testing.T) {
   195  	ctx := tempTransaction(t)
   196  
   197  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
   198  	tradeStore := sqlstore.NewTrades(connectionSource)
   199  
   200  	startTime := time.Unix(StartTime, 0)
   201  	insertCandlesTestData(t, ctx, tradeStore, startTime, 90, 3, startPrice, priceIncrement, size,
   202  		1*time.Second)
   203  
   204  	last := int32(1)
   205  	pagination, _ := entities.NewCursorPagination(nil, nil, &last, nil, false)
   206  	_, candleID, _ := candleStore.GetCandleIDForIntervalAndMarket(ctx, "1 Minute", testMarket)
   207  	candles, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime,
   208  		nil, pagination)
   209  	if err != nil {
   210  		t.Fatalf("failed to get candles:%s", err)
   211  	}
   212  
   213  	assert.Equal(t, 1, len(candles))
   214  
   215  	lastCandle := createCandle(startTime.Add(60*time.Second),
   216  		startTime.Add(89*time.Second).Add(2*time.Microsecond), 181, 270, 270, 181, 900, 202950)
   217  	assert.Equal(t, lastCandle, candles[0])
   218  }
   219  
   220  func TestCandlesGetForDifferentIntervalAndTimeBounds(t *testing.T) {
   221  	ctx := tempTransaction(t)
   222  
   223  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
   224  	tradeStore := sqlstore.NewTrades(connectionSource)
   225  
   226  	startTime := time.Unix(StartTime, 0)
   227  	insertCandlesTestData(t, ctx, tradeStore, startTime, totalBlocks, tradesPerBlock, startPrice, priceIncrement, size, blockIntervalDur)
   228  
   229  	testInterval(t, ctx, startTime, nil, nil, candleStore, "1 Minute", 60)
   230  	testInterval(t, ctx, startTime, nil, nil, candleStore, "5 Minutes", 300)
   231  	testInterval(t, ctx, startTime, nil, nil, candleStore, "15 Minutes", 900)
   232  	testInterval(t, ctx, startTime, nil, nil, candleStore, "1 hour", 3600)
   233  
   234  	from := startTime.Add(5 * time.Minute)
   235  	to := startTime.Add(35 * time.Minute)
   236  
   237  	testInterval(t, ctx, startTime, &from, &to, candleStore, "1 Minute", 60)
   238  	testInterval(t, ctx, startTime, &from, &to, candleStore, "5 Minutes", 300)
   239  
   240  	testInterval(t, ctx, startTime, nil, &to, candleStore, "1 Minute", 60)
   241  	testInterval(t, ctx, startTime, nil, &to, candleStore, "5 Minutes", 300)
   242  
   243  	testInterval(t, ctx, startTime, &from, nil, candleStore, "1 Minute", 60)
   244  	testInterval(t, ctx, startTime, &from, nil, candleStore, "5 Minutes", 300)
   245  }
   246  
   247  func testInterval(t *testing.T, ctx context.Context, tradeDataStartTime time.Time, fromTime *time.Time, toTime *time.Time, candleStore *sqlstore.Candles, interval string,
   248  	intervalSeconds int,
   249  ) {
   250  	t.Helper()
   251  	intervalDur := time.Duration(intervalSeconds) * time.Second
   252  
   253  	pagination, _ := entities.NewCursorPagination(nil, nil, nil, nil, false)
   254  	_, candleID, _ := candleStore.GetCandleIDForIntervalAndMarket(ctx, interval, testMarket)
   255  	candles, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, fromTime,
   256  		toTime, pagination)
   257  	if err != nil {
   258  		t.Fatalf("failed to get candles:%s", err)
   259  	}
   260  
   261  	tradeDataTimeSpanSeconds := totalBlocks * blockIntervalSeconds
   262  	tradeDataEndTime := tradeDataStartTime.Add(time.Duration(tradeDataTimeSpanSeconds) * time.Second)
   263  
   264  	var candlesStartTime time.Time
   265  	if fromTime != nil && fromTime.After(tradeDataStartTime) {
   266  		candlesStartTime = *fromTime
   267  	} else {
   268  		candlesStartTime = tradeDataStartTime
   269  	}
   270  
   271  	var candlesEndTime time.Time
   272  	if toTime != nil && toTime.Before(tradeDataEndTime) {
   273  		candlesEndTime = *toTime
   274  	} else {
   275  		candlesEndTime = tradeDataEndTime
   276  	}
   277  
   278  	candleSpan := candlesEndTime.Sub(candlesStartTime)
   279  
   280  	expectedNumCandles := int(candleSpan.Seconds() / float64(intervalSeconds))
   281  
   282  	if toTime == nil {
   283  		expectedNumCandles = expectedNumCandles + 1
   284  	}
   285  
   286  	assert.Equal(t, expectedNumCandles, len(candles))
   287  
   288  	blocksPerInterval := intervalSeconds / blockIntervalSeconds
   289  
   290  	skippedTrades := int(candlesStartTime.Sub(tradeDataStartTime).Seconds()/blockIntervalSeconds) * tradesPerBlock
   291  
   292  	for idx := 0; idx < expectedNumCandles-1; idx++ {
   293  		periodStart := candlesStartTime.Add(time.Duration(idx) * intervalDur)
   294  		tradesAtOpen := skippedTrades + idx*tradesPerBlock*blocksPerInterval
   295  		tradesAtClose := skippedTrades + (idx+1)*tradesPerBlock*blocksPerInterval
   296  		expectedVolume := tradesPerBlock * blocksPerInterval * size
   297  		candle := candles[idx]
   298  		expectedCandle := createCandle(periodStart,
   299  			periodStart.Add(time.Duration(tradesPerBlock-1)*time.Microsecond).Add(intervalDur).Add(-1*blockIntervalDur),
   300  			startPrice+tradesAtOpen, tradesAtClose, tradesAtClose, startPrice+tradesAtOpen,
   301  			expectedVolume, candle.Notional)
   302  		assert.Equal(t, expectedCandle, candle)
   303  	}
   304  }
   305  
   306  func createCandle(periodStart time.Time, lastUpdate time.Time, open int, close int, high int, low int, volume int, notional uint64) entities.Candle {
   307  	return entities.Candle{
   308  		PeriodStart:        periodStart,
   309  		LastUpdateInPeriod: lastUpdate,
   310  		Open:               decimal.NewFromInt(int64(open)),
   311  		Close:              decimal.NewFromInt(int64(close)),
   312  		High:               decimal.NewFromInt(int64(high)),
   313  		Low:                decimal.NewFromInt(int64(low)),
   314  		Volume:             uint64(volume),
   315  		Notional:           notional,
   316  	}
   317  }
   318  
   319  //nolint:unparam
   320  func insertCandlesTestData(t *testing.T, ctx context.Context, tradeStore *sqlstore.Trades, startTime time.Time, numBlocks int,
   321  	tradePerBlock int, startPrice int, priceIncrement int, size int, blockIntervalDur time.Duration,
   322  ) {
   323  	t.Helper()
   324  	bs := sqlstore.NewBlocks(connectionSource)
   325  
   326  	var blocks []entities.Block
   327  	for i := 0; i < numBlocks; i++ {
   328  		blocks = append(blocks, addTestBlockForTime(t, ctx, bs, startTime.Add(time.Duration(i)*blockIntervalDur)))
   329  	}
   330  
   331  	for _, block := range blocks {
   332  		for seqNum := 0; seqNum < tradePerBlock; seqNum++ {
   333  			trade := createTestTrade(t, startPrice, size, block, seqNum)
   334  			err := tradeStore.Add(trade)
   335  			if err != nil {
   336  				t.Fatalf("failed to add trade to store:%s", err)
   337  			}
   338  			startPrice = startPrice + priceIncrement
   339  		}
   340  	}
   341  
   342  	_, err := tradeStore.Flush(ctx)
   343  	assert.NoError(t, err)
   344  }
   345  
   346  func insertTestTrade(t *testing.T, ctx context.Context, tradeStore *sqlstore.Trades, price int, size int, block entities.Block, seqNum int) {
   347  	t.Helper()
   348  	trade := createTestTrade(t, price, size, block, seqNum)
   349  	insertTrade(t, ctx, tradeStore, trade)
   350  }
   351  
   352  func insertTrade(t *testing.T, ctx context.Context, tradeStore *sqlstore.Trades, trade *entities.Trade) *entities.Trade {
   353  	t.Helper()
   354  	err := tradeStore.Add(trade)
   355  	tradeStore.Flush(ctx)
   356  	if err != nil {
   357  		t.Fatalf("failed to add trade to store:%s", err)
   358  	}
   359  
   360  	return trade
   361  }
   362  
   363  func createTestTrade(t *testing.T, price int, size int, block entities.Block, seqNum int) *entities.Trade {
   364  	t.Helper()
   365  	proto := &types.Trade{
   366  		Type:      types.Trade_TYPE_DEFAULT,
   367  		Id:        GenerateID(),
   368  		Price:     strconv.Itoa(price),
   369  		Size:      uint64(size),
   370  		MarketId:  testMarket,
   371  		Buyer:     GenerateID(),
   372  		Seller:    GenerateID(),
   373  		Aggressor: types.Side_SIDE_SELL,
   374  		BuyOrder:  GenerateID(),
   375  		SellOrder: GenerateID(),
   376  	}
   377  
   378  	trade, err := entities.TradeFromProto(proto, generateTxHash(), block.VegaTime, uint64(seqNum))
   379  	if err != nil {
   380  		t.Fatalf("failed to create trade from proto:%s", err)
   381  	}
   382  	return trade
   383  }
   384  
   385  func TestCandlesCursorPagination(t *testing.T) {
   386  	ctx := tempTransaction(t)
   387  
   388  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
   389  	tradeStore := sqlstore.NewTrades(connectionSource)
   390  
   391  	startTime := time.Unix(StartTime, 0)
   392  	insertCandlesTestData(t, ctx, tradeStore, startTime, totalBlocks, tradesPerBlock, startPrice, priceIncrement, size, blockIntervalDur)
   393  
   394  	_, candleID, err := candleStore.GetCandleIDForIntervalAndMarket(ctx, "1 Minute", testMarket)
   395  	if err != nil {
   396  		t.Fatalf("getting existing candleDescriptor id:%s", err)
   397  	}
   398  
   399  	pagination, _ := entities.NewCursorPagination(nil, nil, nil, nil, false)
   400  	// retrieve all candles without pagination to use for test validation
   401  	allCandles, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime,
   402  		nil, pagination)
   403  	if err != nil {
   404  		t.Fatalf("failed to get candles:%s", err)
   405  	}
   406  
   407  	require.Equal(t, 167, len(allCandles))
   408  
   409  	t.Run("should return the first candles when first is provided with no after", func(t *testing.T) {
   410  		first := int32(10)
   411  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   412  		require.NoError(t, err)
   413  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   414  		require.NoError(t, err)
   415  		require.Equal(t, 10, len(candles))
   416  		assert.Equal(t, allCandles[0], candles[0])
   417  		assert.Equal(t, allCandles[9], candles[9])
   418  		assert.Equal(t, entities.PageInfo{
   419  			HasNextPage:     true,
   420  			HasPreviousPage: false,
   421  			StartCursor:     allCandles[0].Cursor().Encode(),
   422  			EndCursor:       allCandles[9].Cursor().Encode(),
   423  		}, pageInfo)
   424  	})
   425  
   426  	t.Run("should return the first candles when first is provided with no after - newest first", func(t *testing.T) {
   427  		first := int32(10)
   428  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   429  		require.NoError(t, err)
   430  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   431  		require.NoError(t, err)
   432  		lastIndex := len(allCandles) - 1
   433  		require.Equal(t, 10, len(candles))
   434  		assert.Equal(t, allCandles[lastIndex], candles[0])
   435  		assert.Equal(t, allCandles[lastIndex-9], candles[9])
   436  		assert.Equal(t, entities.PageInfo{
   437  			HasNextPage:     true,
   438  			HasPreviousPage: false,
   439  			StartCursor:     allCandles[lastIndex].Cursor().Encode(),
   440  			EndCursor:       allCandles[lastIndex-9].Cursor().Encode(),
   441  		}, pageInfo)
   442  	})
   443  
   444  	t.Run("should return the last page of candles when last is provided with no before", func(t *testing.T) {
   445  		last := int32(10)
   446  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   447  		require.NoError(t, err)
   448  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   449  		require.NoError(t, err)
   450  		require.Equal(t, 10, len(candles))
   451  		assert.Equal(t, allCandles[157], candles[0])
   452  		assert.Equal(t, allCandles[166], candles[9])
   453  		assert.Equal(t, entities.PageInfo{
   454  			HasNextPage:     false,
   455  			HasPreviousPage: true,
   456  			StartCursor:     allCandles[157].Cursor().Encode(),
   457  			EndCursor:       allCandles[166].Cursor().Encode(),
   458  		}, pageInfo)
   459  	})
   460  
   461  	t.Run("should return the last page of candles when last is provided with no before - newest first", func(t *testing.T) {
   462  		last := int32(10)
   463  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   464  		require.NoError(t, err)
   465  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   466  		require.NoError(t, err)
   467  		require.Equal(t, 10, len(candles))
   468  		assert.Equal(t, allCandles[9], candles[0])
   469  		assert.Equal(t, allCandles[0], candles[9])
   470  		assert.Equal(t, entities.PageInfo{
   471  			HasNextPage:     false,
   472  			HasPreviousPage: true,
   473  			StartCursor:     allCandles[9].Cursor().Encode(),
   474  			EndCursor:       allCandles[0].Cursor().Encode(),
   475  		}, pageInfo)
   476  	})
   477  
   478  	t.Run("should return the requested page of candles when first and after are provided", func(t *testing.T) {
   479  		first := int32(10)
   480  		after := allCandles[99].Cursor().Encode()
   481  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   482  		require.NoError(t, err)
   483  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   484  		require.NoError(t, err)
   485  		require.Equal(t, 10, len(candles))
   486  		assert.Equal(t, allCandles[100], candles[0])
   487  		assert.Equal(t, allCandles[109], candles[9])
   488  		assert.Equal(t, entities.PageInfo{
   489  			HasNextPage:     true,
   490  			HasPreviousPage: true,
   491  			StartCursor:     allCandles[100].Cursor().Encode(),
   492  			EndCursor:       allCandles[109].Cursor().Encode(),
   493  		}, pageInfo)
   494  	})
   495  
   496  	t.Run("should return the requested page of candles when first and after are provided - newest first", func(t *testing.T) {
   497  		first := int32(10)
   498  		after := allCandles[99].Cursor().Encode()
   499  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   500  		require.NoError(t, err)
   501  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   502  		require.NoError(t, err)
   503  		require.Equal(t, 10, len(candles))
   504  		assert.Equal(t, allCandles[98], candles[0])
   505  		assert.Equal(t, allCandles[89], candles[9])
   506  		assert.Equal(t, entities.PageInfo{
   507  			HasNextPage:     true,
   508  			HasPreviousPage: true,
   509  			StartCursor:     allCandles[98].Cursor().Encode(),
   510  			EndCursor:       allCandles[89].Cursor().Encode(),
   511  		}, pageInfo)
   512  	})
   513  
   514  	t.Run("Should return the requested page of candles when last and before are provided", func(t *testing.T) {
   515  		last := int32(10)
   516  		before := allCandles[100].Cursor().Encode()
   517  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   518  		require.NoError(t, err)
   519  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   520  		require.NoError(t, err)
   521  		require.Equal(t, 10, len(candles))
   522  		assert.Equal(t, allCandles[90], candles[0])
   523  		assert.Equal(t, allCandles[99], candles[9])
   524  		assert.Equal(t, entities.PageInfo{
   525  			HasNextPage:     true,
   526  			HasPreviousPage: true,
   527  			StartCursor:     allCandles[90].Cursor().Encode(),
   528  			EndCursor:       allCandles[99].Cursor().Encode(),
   529  		}, pageInfo)
   530  	})
   531  
   532  	t.Run("Should return the requested page of candles when last and before are provided - newest first", func(t *testing.T) {
   533  		last := int32(10)
   534  		before := allCandles[100].Cursor().Encode()
   535  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   536  		require.NoError(t, err)
   537  		candles, pageInfo, err := candleStore.GetCandleDataForTimeSpan(ctx, candleID, &startTime, nil, pagination)
   538  		require.NoError(t, err)
   539  		require.Equal(t, 10, len(candles))
   540  		assert.Equal(t, allCandles[110], candles[0])
   541  		assert.Equal(t, allCandles[101], candles[9])
   542  		assert.Equal(t, entities.PageInfo{
   543  			HasNextPage:     true,
   544  			HasPreviousPage: true,
   545  			StartCursor:     allCandles[110].Cursor().Encode(),
   546  			EndCursor:       allCandles[101].Cursor().Encode(),
   547  		}, pageInfo)
   548  	})
   549  }
   550  
   551  func TestCandlesBlockInterval(t *testing.T) {
   552  	ctx := tempTransaction(t)
   553  
   554  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
   555  
   556  	tradeStore := sqlstore.NewTrades(connectionSource)
   557  
   558  	startTime := time.Unix(StartTime, 0)
   559  	insertCandlesTestData(t, ctx, tradeStore, startTime, totalBlocks, tradesPerBlock, startPrice, priceIncrement, size, time.Minute)
   560  
   561  	candles, err := candleStore.GetCandlesForMarket(ctx, testMarket)
   562  	require.NoError(t, err)
   563  	first := int32(10)
   564  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   565  	require.NoError(t, err)
   566  
   567  	candleData, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candles["block"], nil,
   568  		nil, pagination)
   569  	if err != nil {
   570  		t.Fatalf("failed to get candles with pagination:%s", err)
   571  	}
   572  
   573  	assert.Equal(t, 10, len(candleData))
   574  }
   575  
   576  func TestCandlesFillBeforeFirstCandle(t *testing.T) {
   577  	ctx := tempTransaction(t)
   578  
   579  	candleStore := sqlstore.NewCandles(ctx, connectionSource, candlesv2.NewDefaultConfig().CandleStore)
   580  
   581  	candles, err := candleStore.GetCandlesForMarket(ctx, testMarket)
   582  	require.NoError(t, err)
   583  	first := int32(10)
   584  	pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   585  	require.NoError(t, err)
   586  
   587  	candleData, _, err := candleStore.GetCandleDataForTimeSpan(ctx, candles["block"], nil,
   588  		nil, pagination)
   589  	if err != nil {
   590  		t.Fatalf("failed to get candles with pagination:%s", err)
   591  	}
   592  
   593  	assert.Equal(t, 0, len(candleData))
   594  }