code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/market_data_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  	"bufio"
    20  	"context"
    21  	"encoding/csv"
    22  	"encoding/hex"
    23  	"encoding/json"
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  	"strconv"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"code.vegaprotocol.io/vega/datanode/entities"
    33  	"code.vegaprotocol.io/vega/datanode/sqlstore"
    34  	"code.vegaprotocol.io/vega/libs/num"
    35  	"code.vegaprotocol.io/vega/libs/ptr"
    36  	"code.vegaprotocol.io/vega/protos/vega"
    37  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    38  
    39  	"github.com/shopspring/decimal"
    40  	"github.com/stretchr/testify/assert"
    41  	"github.com/stretchr/testify/require"
    42  )
    43  
    44  const (
    45  	csvColumnMarket = iota
    46  	_
    47  	csvColumnVegaTime
    48  	csvColumnMarkPrice
    49  	csvColumnBestBidPrice
    50  	csvColumnBestBidVolume
    51  	csvColumnBestOfferPrice
    52  	csvColumnBestOfferVolume
    53  	csvColumnBestStaticBidPrice
    54  	csvColumnBestStaticBidVolume
    55  	csvColumnBestStaticOfferPrice
    56  	csvColumnBestStaticOfferVolume
    57  	csvColumnMidPrice
    58  	csvColumnStaticMidPrice
    59  	csvColumnOpenInterest
    60  	csvColumnAuctionEnd
    61  	csvColumnAuctionStart
    62  	csvColumnIndicativePrice
    63  	csvColumnIndicativeVolume
    64  	csvColumnMarketTradingMode
    65  	csvColumnAuctionTrigger
    66  	csvColumnExtensionTrigger
    67  	csvColumnTargetStake
    68  	csvColumnSuppliedStake
    69  	csvColumnPriceMonitoringBounds
    70  	csvColumnMarketValueProxy
    71  	csvColumnLiquidityProviderFeeShares
    72  	csvColumnMarketState
    73  	csvColumnMarketGrowth
    74  	csvColumnLastTradedPrice
    75  )
    76  
    77  func Test_MarketData(t *testing.T) {
    78  	t.Run("Add should insert a valid market data record", shouldInsertAValidMarketDataRecord)
    79  	t.Run("Get should return the latest market data record for a given market", getLatestMarketData)
    80  	t.Run("GetHistoricMarketData should return the all the market data between dates given for the specified market", getAllForMarketBetweenDates)
    81  	t.Run("GetHistoricMarketData should return all market data for a given market with date greater than or equal to the given date", getForMarketFromDate)
    82  	t.Run("GetHistoricMarketData should return all market data for a given market with date less than or equal to the given date", getForMarketToDate)
    83  	t.Run("GetHistoricMarketData should return all market data when no start or end is provided", TestGetAllMarketData)
    84  	t.Run("Add should work for all valid enumerations values of auction trigger", shouldWorkForAllValuesOfAuctionTrigger)
    85  	t.Run("Add should work for all valid enumerations values of composite price type", shouldWorkForAllValuesOfCompositePriceType)
    86  }
    87  
    88  func shouldWorkForAllValuesOfCompositePriceType(t *testing.T) {
    89  	var priceType vegapb.CompositePriceType
    90  	enums := getEnums(t, priceType)
    91  	assert.Len(t, enums, 4)
    92  
    93  	for e, pt := range enums {
    94  		t.Run(pt, func(t *testing.T) {
    95  			ctx := tempTransaction(t)
    96  
    97  			addMarketData(t, ctx, "AUCTION_TRIGGER_LIQUIDITY", pt)
    98  			var got entities.MarketData
    99  
   100  			err := connectionSource.QueryRow(ctx, `select mark_price_type from market_data`).Scan(&got.MarkPriceType)
   101  			require.NoError(t, err)
   102  
   103  			mdProto := got.ToProto()
   104  
   105  			assert.Equal(t, vegapb.CompositePriceType(e), mdProto.MarkPriceType)
   106  		})
   107  	}
   108  }
   109  
   110  func TestGetPAPState(t *testing.T) {
   111  	ctx := tempTransaction(t)
   112  
   113  	addMarketData(t, ctx, "AUCTION_TRIGGER_LIQUIDITY", "COMPOSITE_PRICE_TYPE_LAST_TRADE")
   114  	var got entities.MarketData
   115  
   116  	err := connectionSource.QueryRow(ctx, `select active_protocol_automated_purchase from market_data`).Scan(&got.ActiveProtocolAutomatedPurchase)
   117  	require.NoError(t, err)
   118  
   119  	require.Equal(t, "1", got.ActiveProtocolAutomatedPurchase.Id)
   120  	require.Equal(t, "2", *got.ActiveProtocolAutomatedPurchase.OrderId)
   121  }
   122  
   123  func addMarketData(t *testing.T, ctx context.Context, trigger, priceType string) {
   124  	t.Helper()
   125  	bs := sqlstore.NewBlocks(connectionSource)
   126  	block := addTestBlock(t, ctx, bs)
   127  
   128  	md := sqlstore.NewMarketData(connectionSource)
   129  	orderID := "2"
   130  	err := md.Add(&entities.MarketData{
   131  		Market:            entities.MarketID("deadbeef"),
   132  		MarketTradingMode: "TRADING_MODE_MONITORING_AUCTION",
   133  		MarketState:       "STATE_ACTIVE",
   134  		AuctionTrigger:    trigger,
   135  		ExtensionTrigger:  trigger,
   136  		MarkPriceType:     priceType,
   137  		PriceMonitoringBounds: []*vega.PriceMonitoringBounds{
   138  			{
   139  				MinValidPrice: "1",
   140  				MaxValidPrice: "2",
   141  				Trigger: &vega.PriceMonitoringTrigger{
   142  					Horizon:          100,
   143  					Probability:      "0.5",
   144  					AuctionExtension: 200,
   145  				},
   146  				ReferencePrice: "3",
   147  			},
   148  		},
   149  		VegaTime: block.VegaTime,
   150  		ActiveProtocolAutomatedPurchase: &vegapb.ProtocolAutomatedPurchaseData{
   151  			Id:      "1",
   152  			OrderId: &orderID,
   153  		},
   154  	})
   155  	require.NoError(t, err)
   156  
   157  	_, err = md.Flush(ctx)
   158  	require.NoError(t, err)
   159  }
   160  
   161  func shouldWorkForAllValuesOfAuctionTrigger(t *testing.T) {
   162  	var auctionTrigger vegapb.AuctionTrigger
   163  	enums := getEnums(t, auctionTrigger)
   164  	require.Len(t, enums, 10)
   165  
   166  	for e, trigger := range enums {
   167  		t.Run(trigger, func(t *testing.T) {
   168  			ctx := tempTransaction(t)
   169  
   170  			addMarketData(t, ctx, trigger, "COMPOSITE_PRICE_TYPE_LAST_TRADE")
   171  			var got entities.MarketData
   172  
   173  			err := connectionSource.QueryRow(ctx, `select auction_trigger from market_data`).Scan(&got.AuctionTrigger)
   174  			require.NoError(t, err)
   175  
   176  			mdProto := got.ToProto()
   177  
   178  			assert.Equal(t, vegapb.AuctionTrigger(e), mdProto.Trigger)
   179  		})
   180  	}
   181  }
   182  
   183  func shouldInsertAValidMarketDataRecord(t *testing.T) {
   184  	ctx := tempTransaction(t)
   185  
   186  	bs := sqlstore.NewBlocks(connectionSource)
   187  	md := sqlstore.NewMarketData(connectionSource)
   188  
   189  	var rowCount int
   190  
   191  	err := connectionSource.QueryRow(ctx, `select count(*) from market_data`).Scan(&rowCount)
   192  	require.NoError(t, err)
   193  	assert.Equal(t, 0, rowCount)
   194  
   195  	block := addTestBlock(t, ctx, bs)
   196  
   197  	err = md.Add(&entities.MarketData{
   198  		Market:            entities.MarketID("deadbeef"),
   199  		MarketTradingMode: "TRADING_MODE_MONITORING_AUCTION",
   200  		MarketState:       "STATE_ACTIVE",
   201  		AuctionTrigger:    "AUCTION_TRIGGER_LIQUIDITY",
   202  		ExtensionTrigger:  "AUCTION_TRIGGER_UNSPECIFIED",
   203  		MarkPriceType:     "COMPOSITE_PRICE_TYPE_UNSPECIFIED",
   204  		PriceMonitoringBounds: []*vega.PriceMonitoringBounds{
   205  			{
   206  				MinValidPrice: "1",
   207  				MaxValidPrice: "2",
   208  				Trigger: &vega.PriceMonitoringTrigger{
   209  					Horizon:          100,
   210  					Probability:      "0.5",
   211  					AuctionExtension: 200,
   212  				},
   213  				ReferencePrice: "3",
   214  			},
   215  		},
   216  		VegaTime: block.VegaTime,
   217  	})
   218  	require.NoError(t, err)
   219  
   220  	_, err = md.Flush(ctx)
   221  	require.NoError(t, err)
   222  
   223  	err = connectionSource.QueryRow(ctx, `select count(*) from market_data`).Scan(&rowCount)
   224  	assert.NoError(t, err)
   225  	assert.Equal(t, 1, rowCount)
   226  }
   227  
   228  func getLatestMarketData(t *testing.T) {
   229  	ctx := tempTransaction(t)
   230  
   231  	store, err := setupMarketData(t, ctx)
   232  	if err != nil {
   233  		t.Fatalf("could not set up test: %s", err)
   234  	}
   235  
   236  	marketID := entities.MarketID("8cc0e020c0bc2f9eba77749d81ecec8283283b85941722c2cb88318aaf8b8cd8")
   237  
   238  	want := entities.MarketData{
   239  		MarkPrice:             mustParseDecimal(t, "999992587"),
   240  		BestBidPrice:          mustParseDecimal(t, "1000056152"),
   241  		BestBidVolume:         3,
   242  		BestOfferPrice:        mustParseDecimal(t, "999945379"),
   243  		BestOfferVolume:       1,
   244  		BestStaticBidPrice:    mustParseDecimal(t, "1000056152"),
   245  		BestStaticBidVolume:   3,
   246  		BestStaticOfferPrice:  mustParseDecimal(t, "999945379"),
   247  		BestStaticOfferVolume: 1,
   248  		MidPrice:              mustParseDecimal(t, "1000000765"),
   249  		StaticMidPrice:        mustParseDecimal(t, "1000000765"),
   250  		Market:                marketID,
   251  		OpenInterest:          27,
   252  		AuctionEnd:            1644573937314794695,
   253  		AuctionStart:          1644573911314794695,
   254  		IndicativePrice:       mustParseDecimal(t, "1000026624"),
   255  		IndicativeVolume:      3,
   256  		MarketTradingMode:     "TRADING_MODE_MONITORING_AUCTION",
   257  		MarketState:           "STATE_ACTIVE",
   258  		AuctionTrigger:        "AUCTION_TRIGGER_LIQUIDITY",
   259  		ExtensionTrigger:      "AUCTION_TRIGGER_UNSPECIFIED",
   260  		MarkPriceType:         "COMPOSITE_PRICE_TYPE_LAST_TRADE",
   261  		TargetStake:           mustParseDecimal(t, "67499499622"),
   262  		SuppliedStake:         mustParseDecimal(t, "50000000000"),
   263  		PriceMonitoringBounds: []*vega.PriceMonitoringBounds{
   264  			{
   265  				MinValidPrice: "1",
   266  				MaxValidPrice: "2",
   267  				Trigger: &vega.PriceMonitoringTrigger{
   268  					Horizon:          100,
   269  					Probability:      "0.5",
   270  					AuctionExtension: 200,
   271  				},
   272  				ReferencePrice: "3",
   273  			},
   274  		},
   275  		MarketValueProxy: "194290093211464.7413030152957024",
   276  		LiquidityProviderFeeShares: []*vega.LiquidityProviderFeeShare{
   277  			{
   278  				Party:                 "af2bb48edd738353fcd7a2b6cea4821dd2382ec95497954535278dfbfff7b5b5",
   279  				EquityLikeShare:       "1",
   280  				AverageEntryValuation: "50000000000",
   281  				AverageScore:          "123",
   282  			},
   283  		},
   284  		MarketGrowth:    num.DecimalZero(),
   285  		LastTradedPrice: mustParseDecimal(t, "999992588"),
   286  		ProductData: &entities.ProductData{
   287  			ProductData: &vega.ProductData{
   288  				Data: &vega.ProductData_PerpetualData{
   289  					PerpetualData: &vega.PerpetualData{
   290  						InternalCompositePrice:         "100",
   291  						NextInternalCompositePriceCalc: 200,
   292  						InternalCompositePriceType:     vega.CompositePriceType_COMPOSITE_PRICE_TYPE_LAST_TRADE,
   293  					},
   294  				},
   295  			},
   296  		},
   297  	}
   298  	got, err := store.GetMarketDataByID(ctx, "8cc0e020c0bc2f9eba77749d81ecec8283283b85941722c2cb88318aaf8b8cd8")
   299  	require.NoError(t, err)
   300  
   301  	require.Truef(t, want.Equal(got), "want: %#v\ngot: %#v\n", want, got)
   302  }
   303  
   304  func getAllForMarketBetweenDates(t *testing.T) {
   305  	ctx := tempTransaction(t)
   306  
   307  	store, err := setupMarketData(t, ctx)
   308  	if err != nil {
   309  		t.Fatalf("could not set up test: %s", err)
   310  	}
   311  
   312  	market := "8cc0e020c0bc2f9eba77749d81ecec8283283b85941722c2cb88318aaf8b8cd8"
   313  
   314  	startDate := ptr.From(time.Date(2022, 2, 11, 10, 5, 30, 0, time.UTC))
   315  	endDate := ptr.From(time.Date(2022, 2, 11, 10, 6, 0, 0, time.UTC))
   316  
   317  	t.Run("should return all results if no cursor pagination is provided", func(t *testing.T) {
   318  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, entities.CursorPagination{})
   319  		assert.NoError(t, err)
   320  		assert.Equal(t, 9, len(got))
   321  		assert.Equal(t, entities.PageInfo{
   322  			HasNextPage:     false,
   323  			HasPreviousPage: false,
   324  			StartCursor: entities.NewCursor(
   325  				entities.MarketDataCursor{
   326  					SyntheticTime: time.Date(2022, 2, 11, 10, 5, 31, 175000, time.UTC).Local(),
   327  				}.String()).Encode(),
   328  			EndCursor: entities.NewCursor(
   329  				entities.MarketDataCursor{
   330  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   331  				}.String()).Encode(),
   332  		}, pageInfo)
   333  	})
   334  
   335  	t.Run("should return all results if no cursor pagination is provided - newest first", func(t *testing.T) {
   336  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, entities.CursorPagination{NewestFirst: true})
   337  		assert.NoError(t, err)
   338  		assert.Equal(t, 9, len(got))
   339  		assert.Equal(t, entities.PageInfo{
   340  			HasNextPage:     false,
   341  			HasPreviousPage: false,
   342  			StartCursor: entities.NewCursor(
   343  				entities.MarketDataCursor{
   344  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   345  				}.String()).Encode(),
   346  			EndCursor: entities.NewCursor(
   347  				entities.MarketDataCursor{
   348  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 31, 175000, time.UTC).Local(),
   349  				}.String()).Encode(),
   350  		}, pageInfo)
   351  	})
   352  
   353  	t.Run("should return page of results if cursor pagination is provided with first", func(t *testing.T) {
   354  		first := int32(5)
   355  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   356  		require.NoError(t, err)
   357  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   358  		require.NoError(t, err)
   359  		assert.Equal(t, 5, len(got))
   360  		assert.Equal(t, entities.PageInfo{
   361  			HasNextPage:     true,
   362  			HasPreviousPage: false,
   363  			StartCursor: entities.NewCursor(
   364  				entities.MarketDataCursor{
   365  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 31, 175000, time.UTC).Local(),
   366  				}.String()).Encode(),
   367  			EndCursor: entities.NewCursor(
   368  				entities.MarketDataCursor{
   369  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 36, 179000, time.UTC).Local(),
   370  				}.String()).Encode(),
   371  		}, pageInfo)
   372  	})
   373  
   374  	t.Run("should return page of results if cursor pagination is provided with first - newest first", func(t *testing.T) {
   375  		first := int32(5)
   376  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   377  		require.NoError(t, err)
   378  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   379  		require.NoError(t, err)
   380  		assert.Equal(t, 5, len(got))
   381  		assert.Equal(t, entities.PageInfo{
   382  			HasNextPage:     true,
   383  			HasPreviousPage: false,
   384  			StartCursor: entities.NewCursor(
   385  				entities.MarketDataCursor{
   386  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   387  				}.String()).Encode(),
   388  			EndCursor: entities.NewCursor(
   389  				entities.MarketDataCursor{
   390  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 36, 179000, time.UTC).Local(),
   391  				}.String()).Encode(),
   392  		}, pageInfo)
   393  	})
   394  
   395  	t.Run("should return page of results if forward cursor pagination is provided with first and after parameter", func(t *testing.T) {
   396  		first := int32(5)
   397  		after := entities.NewCursor(entities.MarketDataCursor{
   398  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 32, 176000, time.UTC).Local(),
   399  		}.String()).Encode()
   400  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   401  		require.NoError(t, err)
   402  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   403  		require.NoError(t, err)
   404  		assert.Equal(t, 5, len(got))
   405  		assert.Equal(t, entities.PageInfo{
   406  			HasNextPage:     true,
   407  			HasPreviousPage: true,
   408  			StartCursor: entities.NewCursor(
   409  				entities.MarketDataCursor{
   410  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 33, 177000, time.UTC).Local(),
   411  				}.String()).Encode(),
   412  			EndCursor: entities.NewCursor(
   413  				entities.MarketDataCursor{
   414  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 39, 181000, time.UTC).Local(),
   415  				}.String()).Encode(),
   416  		}, pageInfo)
   417  	})
   418  
   419  	t.Run("should return page of results if forward cursor pagination is provided with first and after parameter - newest first", func(t *testing.T) {
   420  		first := int32(5)
   421  		after := entities.NewCursor(entities.MarketDataCursor{
   422  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 40, 182000, time.UTC).Local(),
   423  		}.String()).Encode()
   424  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   425  		require.NoError(t, err)
   426  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   427  		require.NoError(t, err)
   428  		assert.Equal(t, 5, len(got))
   429  		assert.Equal(t, entities.PageInfo{
   430  			HasNextPage:     true,
   431  			HasPreviousPage: true,
   432  			StartCursor: entities.NewCursor(
   433  				entities.MarketDataCursor{
   434  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 39, 181000, time.UTC).Local(),
   435  				}.String()).Encode(),
   436  			EndCursor: entities.NewCursor(
   437  				entities.MarketDataCursor{
   438  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 33, 177000, time.UTC).Local(),
   439  				}.String()).Encode(),
   440  		}, pageInfo)
   441  	})
   442  
   443  	t.Run("should return page of results if cursor pagination is provided with last", func(t *testing.T) {
   444  		last := int32(5)
   445  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   446  		require.NoError(t, err)
   447  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   448  		require.NoError(t, err)
   449  		assert.Equal(t, 5, len(got))
   450  		assert.Equal(t, entities.PageInfo{
   451  			HasNextPage:     false,
   452  			HasPreviousPage: true,
   453  			StartCursor: entities.NewCursor(
   454  				entities.MarketDataCursor{
   455  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 36, 179000, time.UTC).Local(),
   456  				}.String()).Encode(),
   457  			EndCursor: entities.NewCursor(
   458  				entities.MarketDataCursor{
   459  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   460  				}.String()).Encode(),
   461  		}, pageInfo)
   462  	})
   463  
   464  	t.Run("should return page of results if cursor pagination is provided with last - newest first", func(t *testing.T) {
   465  		last := int32(5)
   466  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   467  		require.NoError(t, err)
   468  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   469  		require.NoError(t, err)
   470  		assert.Equal(t, 5, len(got))
   471  		assert.Equal(t, entities.PageInfo{
   472  			HasNextPage:     false,
   473  			HasPreviousPage: true,
   474  			StartCursor: entities.NewCursor(
   475  				entities.MarketDataCursor{
   476  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 36, 179000, time.UTC).Local(),
   477  				}.String()).Encode(),
   478  			EndCursor: entities.NewCursor(
   479  				entities.MarketDataCursor{
   480  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 31, 175000, time.UTC).Local(),
   481  				}.String()).Encode(),
   482  		}, pageInfo)
   483  	})
   484  
   485  	t.Run("should return page of results if forward cursor pagination is provided with last and before parameter", func(t *testing.T) {
   486  		last := int32(5)
   487  		before := entities.NewCursor(
   488  			entities.MarketDataCursor{
   489  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 40, 182000, time.UTC).Local(),
   490  			}.String()).Encode()
   491  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   492  		require.NoError(t, err)
   493  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   494  		require.NoError(t, err)
   495  		assert.Equal(t, 5, len(got))
   496  		assert.Equal(t, entities.PageInfo{
   497  			HasNextPage:     true,
   498  			HasPreviousPage: true,
   499  			StartCursor: entities.NewCursor(
   500  				entities.MarketDataCursor{
   501  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 33, 177000, time.UTC).Local(),
   502  				}.String()).Encode(),
   503  			EndCursor: entities.NewCursor(
   504  				entities.MarketDataCursor{
   505  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 39, 181000, time.UTC).Local(),
   506  				}.String()).Encode(),
   507  		}, pageInfo)
   508  	})
   509  
   510  	t.Run("should return page of results if forward cursor pagination is provided with last and before parameter - newest first", func(t *testing.T) {
   511  		last := int32(5)
   512  		before := entities.NewCursor(
   513  			entities.MarketDataCursor{
   514  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 32, 176000, time.UTC).Local(),
   515  			}.String()).Encode()
   516  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   517  		require.NoError(t, err)
   518  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, endDate, pagination)
   519  		require.NoError(t, err)
   520  		assert.Equal(t, 5, len(got))
   521  		assert.Equal(t, entities.PageInfo{
   522  			HasNextPage:     true,
   523  			HasPreviousPage: true,
   524  			StartCursor: entities.NewCursor(
   525  				entities.MarketDataCursor{
   526  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 39, 181000, time.UTC).Local(),
   527  				}.String()).Encode(),
   528  			EndCursor: entities.NewCursor(
   529  				entities.MarketDataCursor{
   530  					SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 33, 177000, time.UTC).Local(),
   531  				}.String()).Encode(),
   532  		}, pageInfo)
   533  	})
   534  }
   535  
   536  func getForMarketFromDate(t *testing.T) {
   537  	ctx := tempTransaction(t)
   538  
   539  	store, err := setupMarketData(t, ctx)
   540  	require.NoError(t, err)
   541  
   542  	startDate := ptr.From(time.Date(2022, 2, 11, 10, 5, 0, 0, time.UTC))
   543  
   544  	market := "8cc0e020c0bc2f9eba77749d81ecec8283283b85941722c2cb88318aaf8b8cd8"
   545  
   546  	t.Run("should return all results if no cursor pagination is provided", func(t *testing.T) {
   547  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, entities.CursorPagination{})
   548  		assert.NoError(t, err)
   549  		assert.Equal(t, 32, len(got))
   550  		assert.Equal(t, entities.PageInfo{
   551  			HasNextPage:     false,
   552  			HasPreviousPage: false,
   553  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   554  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 0o0, 152000, time.UTC).Local(),
   555  			}.String()).Encode(),
   556  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   557  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   558  			}.String()).Encode(),
   559  		}, pageInfo)
   560  	})
   561  
   562  	t.Run("should return all results if no cursor pagination is provided - newest first", func(t *testing.T) {
   563  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, entities.CursorPagination{NewestFirst: true})
   564  		assert.NoError(t, err)
   565  		assert.Equal(t, 32, len(got))
   566  		assert.Equal(t, entities.PageInfo{
   567  			HasNextPage:     false,
   568  			HasPreviousPage: false,
   569  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   570  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   571  			}.String()).Encode(),
   572  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   573  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 0o0, 152000, time.UTC).Local(),
   574  			}.String()).Encode(),
   575  		}, pageInfo)
   576  	})
   577  
   578  	t.Run("should return a page of results if cursor pagination is provided with first", func(t *testing.T) {
   579  		first := int32(5)
   580  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   581  		require.NoError(t, err)
   582  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   583  		assert.NoError(t, err)
   584  		assert.Equal(t, 5, len(got))
   585  		assert.Equal(t, entities.PageInfo{
   586  			HasNextPage:     true,
   587  			HasPreviousPage: false,
   588  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   589  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 0o0, 152000, time.UTC).Local(),
   590  			}.String()).Encode(),
   591  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   592  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 0o5, 156000, time.UTC).Local(),
   593  			}.String()).Encode(),
   594  		}, pageInfo)
   595  	})
   596  
   597  	t.Run("should return a page of results if cursor pagination is provided with first - newest first", func(t *testing.T) {
   598  		first := int32(5)
   599  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   600  		require.NoError(t, err)
   601  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   602  		assert.NoError(t, err)
   603  		assert.Equal(t, 5, len(got))
   604  		assert.Equal(t, entities.PageInfo{
   605  			HasNextPage:     true,
   606  			HasPreviousPage: false,
   607  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   608  				SyntheticTime: time.Date(2022, 2, 11, 10, 5, 41, 183000, time.UTC).Local(),
   609  			}.String()).Encode(),
   610  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   611  				SyntheticTime: time.Date(2022, 2, 11, 10, 5, 36, 179000, time.UTC).Local(),
   612  			}.String()).Encode(),
   613  		}, pageInfo)
   614  	})
   615  
   616  	t.Run("should return a page of results if cursor pagination is provided with first and after", func(t *testing.T) {
   617  		first := int32(5)
   618  		after := entities.NewCursor(entities.MarketDataCursor{
   619  			SyntheticTime: time.Date(2022, 2, 11, 10, 5, 9, 159000, time.UTC).Local(),
   620  		}.String()).Encode()
   621  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   622  		require.NoError(t, err)
   623  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   624  		assert.NoError(t, err)
   625  		assert.Equal(t, 5, len(got))
   626  		assert.Equal(t, entities.PageInfo{
   627  			HasNextPage:     true,
   628  			HasPreviousPage: true,
   629  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   630  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 11, 160000, time.UTC).Local(),
   631  			}.String()).Encode(),
   632  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   633  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 16, 164000, time.UTC).Local(),
   634  			}.String()).Encode(),
   635  		}, pageInfo)
   636  	})
   637  
   638  	t.Run("should return a page of results if cursor pagination is provided with first and after", func(t *testing.T) {
   639  		first := int32(5)
   640  		after := entities.NewCursor(entities.MarketDataCursor{
   641  			SyntheticTime: time.Date(2022, 2, 11, 10, 5, 9, 159000, time.UTC).Local(),
   642  		}.String()).Encode()
   643  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   644  		require.NoError(t, err)
   645  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   646  		assert.NoError(t, err)
   647  		assert.Equal(t, 5, len(got))
   648  		assert.Equal(t, entities.PageInfo{
   649  			HasNextPage:     true,
   650  			HasPreviousPage: true,
   651  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   652  				SyntheticTime: time.Date(2022, 2, 11, 10, 5, 8, 158000, time.UTC).Local(),
   653  			}.String()).Encode(),
   654  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   655  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 0o3, 154000, time.UTC).Local(),
   656  			}.String()).Encode(),
   657  		}, pageInfo)
   658  	})
   659  
   660  	t.Run("should return a page of results if cursor pagination is provided with last", func(t *testing.T) {
   661  		last := int32(5)
   662  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   663  		require.NoError(t, err)
   664  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   665  		assert.NoError(t, err)
   666  		assert.Equal(t, 5, len(got))
   667  		assert.Equal(t, entities.PageInfo{
   668  			HasNextPage:     false,
   669  			HasPreviousPage: true,
   670  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   671  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 36, 179000, time.UTC).Local(),
   672  			}.String()).Encode(),
   673  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   674  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   675  			}.String()).Encode(),
   676  		}, pageInfo)
   677  	})
   678  
   679  	t.Run("should return a page of results if cursor pagination is provided with last - newest first", func(t *testing.T) {
   680  		last := int32(5)
   681  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   682  		require.NoError(t, err)
   683  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   684  		assert.NoError(t, err)
   685  		assert.Equal(t, 5, len(got))
   686  		assert.Equal(t, entities.PageInfo{
   687  			HasNextPage:     false,
   688  			HasPreviousPage: true,
   689  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   690  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 0o5, 156000, time.UTC).Local(),
   691  			}.String()).Encode(),
   692  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   693  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 0o0, 152000, time.UTC).Local(),
   694  			}.String()).Encode(),
   695  		}, pageInfo)
   696  	})
   697  
   698  	t.Run("should return a page of results if cursor pagination is provided with last and before", func(t *testing.T) {
   699  		last := int32(5)
   700  		before := entities.NewCursor(entities.MarketDataCursor{
   701  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 37, 180000, time.UTC).Local(),
   702  		}.String()).Encode()
   703  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   704  		require.NoError(t, err)
   705  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   706  		assert.NoError(t, err)
   707  		assert.Equal(t, 5, len(got))
   708  		assert.Equal(t, entities.PageInfo{
   709  			HasNextPage:     true,
   710  			HasPreviousPage: true,
   711  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   712  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 31, 175000, time.UTC).Local(),
   713  			}.String()).Encode(),
   714  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   715  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 36, 179000, time.UTC).Local(),
   716  			}.String()).Encode(),
   717  		}, pageInfo)
   718  	})
   719  
   720  	t.Run("should return a page of results if cursor pagination is provided with last and before - newest first", func(t *testing.T) {
   721  		last := int32(5)
   722  		before := entities.NewCursor(entities.MarketDataCursor{
   723  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 20, 167000, time.UTC).Local(),
   724  		}.String()).Encode()
   725  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   726  		require.NoError(t, err)
   727  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, startDate, nil, pagination)
   728  		assert.NoError(t, err)
   729  		assert.Equal(t, 5, len(got))
   730  		assert.Equal(t, entities.PageInfo{
   731  			HasNextPage:     true,
   732  			HasPreviousPage: true,
   733  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   734  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 27, 172000, time.UTC).Local(),
   735  			}.String()).Encode(),
   736  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   737  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 22, 168000, time.UTC).Local(),
   738  			}.String()).Encode(),
   739  		}, pageInfo)
   740  	})
   741  }
   742  
   743  func getForMarketToDate(t *testing.T) {
   744  	ctx := tempTransaction(t)
   745  
   746  	store, err := setupMarketData(t, ctx)
   747  	require.NoError(t, err)
   748  
   749  	endDate := ptr.From(time.Date(2022, 2, 11, 10, 2, 0, 0, time.UTC))
   750  
   751  	market := "8cc0e020c0bc2f9eba77749d81ecec8283283b85941722c2cb88318aaf8b8cd8"
   752  
   753  	t.Run("should return all results if no cursor pagination is provided", func(t *testing.T) {
   754  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, entities.CursorPagination{})
   755  		assert.NoError(t, err)
   756  		assert.Equal(t, 18, len(got))
   757  		wantStartCursor := entities.NewCursor(entities.MarketDataCursor{
   758  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   759  		}.String()).Encode()
   760  		wantEndCursor := entities.NewCursor(entities.MarketDataCursor{
   761  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o2, 0o0, 17000, time.UTC).Local(),
   762  		}.String()).Encode()
   763  		assert.Equal(t, entities.PageInfo{
   764  			HasNextPage:     false,
   765  			HasPreviousPage: false,
   766  			StartCursor:     wantStartCursor,
   767  			EndCursor:       wantEndCursor,
   768  		}, pageInfo)
   769  	})
   770  
   771  	t.Run("should return all results if no cursor pagination is provided - newest first", func(t *testing.T) {
   772  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, entities.CursorPagination{NewestFirst: true})
   773  		assert.NoError(t, err)
   774  		assert.Equal(t, 18, len(got))
   775  		wantStartCursor := entities.NewCursor(entities.MarketDataCursor{
   776  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o2, 0o0, 17000, time.UTC).Local(),
   777  		}.String()).Encode()
   778  		wantEndCursor := entities.NewCursor(entities.MarketDataCursor{
   779  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   780  		}.String()).Encode()
   781  		assert.Equal(t, entities.PageInfo{
   782  			HasNextPage:     false,
   783  			HasPreviousPage: false,
   784  			StartCursor:     wantStartCursor,
   785  			EndCursor:       wantEndCursor,
   786  		}, pageInfo)
   787  	})
   788  
   789  	t.Run("should return a page of results if cursor pagination is provided with first", func(t *testing.T) {
   790  		first := int32(10)
   791  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   792  		require.NoError(t, err)
   793  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   794  		assert.NoError(t, err)
   795  		assert.Equal(t, 10, len(got))
   796  		assert.Equal(t, entities.PageInfo{
   797  			HasNextPage:     true,
   798  			HasPreviousPage: false,
   799  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   800  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   801  			}.String()).Encode(),
   802  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   803  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 49, 9000, time.UTC).Local(),
   804  			}.String()).Encode(),
   805  		}, pageInfo)
   806  	})
   807  
   808  	t.Run("should return a page of results if cursor pagination is provided with first - newest first", func(t *testing.T) {
   809  		first := int32(10)
   810  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, true)
   811  		require.NoError(t, err)
   812  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   813  		assert.NoError(t, err)
   814  		assert.Equal(t, 10, len(got))
   815  		assert.Equal(t, entities.PageInfo{
   816  			HasNextPage:     true,
   817  			HasPreviousPage: false,
   818  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   819  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o2, 0o0, 17000, time.UTC).Local(),
   820  			}.String()).Encode(),
   821  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   822  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 47, 8000, time.UTC).Local(),
   823  			}.String()).Encode(),
   824  		}, pageInfo)
   825  	})
   826  
   827  	t.Run("should return a page of results if cursor pagination is provided with first and after", func(t *testing.T) {
   828  		first := int32(10)
   829  		after := entities.NewCursor(entities.MarketDataCursor{
   830  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 49, 9000, time.UTC).Local(),
   831  		}.String()).Encode()
   832  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, false)
   833  		require.NoError(t, err)
   834  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   835  		assert.NoError(t, err)
   836  		assert.Equal(t, 8, len(got))
   837  		assert.Equal(t, entities.PageInfo{
   838  			HasNextPage:     false,
   839  			HasPreviousPage: true,
   840  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   841  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 50, 10000, time.UTC).Local(),
   842  			}.String()).Encode(),
   843  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   844  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o2, 0o0, 17000, time.UTC).Local(),
   845  			}.String()).Encode(),
   846  		}, pageInfo)
   847  	})
   848  
   849  	t.Run("should return a page of results if cursor pagination is provided with first and after - newest first", func(t *testing.T) {
   850  		first := int32(10)
   851  		after := entities.NewCursor(entities.MarketDataCursor{
   852  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 47, 8000, time.UTC).Local(),
   853  		}.String()).Encode()
   854  		pagination, err := entities.NewCursorPagination(&first, &after, nil, nil, true)
   855  		require.NoError(t, err)
   856  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   857  		assert.NoError(t, err)
   858  		assert.Equal(t, 8, len(got))
   859  		assert.Equal(t, entities.PageInfo{
   860  			HasNextPage:     false,
   861  			HasPreviousPage: true,
   862  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   863  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 46, 7000, time.UTC).Local(),
   864  			}.String()).Encode(),
   865  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   866  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   867  			}.String()).Encode(),
   868  		}, pageInfo)
   869  	})
   870  
   871  	t.Run("should return a page of results if cursor pagination is provided with last", func(t *testing.T) {
   872  		last := int32(10)
   873  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, false)
   874  		require.NoError(t, err)
   875  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   876  		assert.NoError(t, err)
   877  		assert.Equal(t, 10, len(got))
   878  		assert.Equal(t, entities.PageInfo{
   879  			HasNextPage:     false,
   880  			HasPreviousPage: true,
   881  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   882  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 47, 8000, time.UTC).Local(),
   883  			}.String()).Encode(),
   884  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   885  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o2, 0o0, 17000, time.UTC).Local(),
   886  			}.String()).Encode(),
   887  		}, pageInfo)
   888  	})
   889  
   890  	t.Run("should return a page of results if cursor pagination is provided with last - newest first", func(t *testing.T) {
   891  		last := int32(10)
   892  		pagination, err := entities.NewCursorPagination(nil, nil, &last, nil, true)
   893  		require.NoError(t, err)
   894  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   895  		assert.NoError(t, err)
   896  		assert.Equal(t, 10, len(got))
   897  		assert.Equal(t, entities.PageInfo{
   898  			HasNextPage:     false,
   899  			HasPreviousPage: true,
   900  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   901  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 49, 9000, time.UTC).Local(),
   902  			}.String()).Encode(),
   903  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   904  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   905  			}.String()).Encode(),
   906  		}, pageInfo)
   907  	})
   908  
   909  	t.Run("should return a page of results if cursor pagination is provided with last and before", func(t *testing.T) {
   910  		last := int32(10)
   911  		before := entities.NewCursor(entities.MarketDataCursor{
   912  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 49, 9000, time.UTC).Local(),
   913  		}.String()).Encode()
   914  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, false)
   915  		require.NoError(t, err)
   916  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   917  		assert.NoError(t, err)
   918  		assert.Equal(t, 9, len(got))
   919  		assert.Equal(t, entities.PageInfo{
   920  			HasNextPage:     true,
   921  			HasPreviousPage: false,
   922  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   923  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   924  			}.String()).Encode(),
   925  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   926  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 47, 8000, time.UTC).Local(),
   927  			}.String()).Encode(),
   928  		}, pageInfo)
   929  	})
   930  
   931  	t.Run("should return a page of results if cursor pagination is provided with last and before - newest first", func(t *testing.T) {
   932  		last := int32(10)
   933  		before := entities.NewCursor(entities.MarketDataCursor{
   934  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 47, 8000, time.UTC).Local(),
   935  		}.String()).Encode()
   936  		pagination, err := entities.NewCursorPagination(nil, nil, &last, &before, true)
   937  		require.NoError(t, err)
   938  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, endDate, pagination)
   939  		assert.NoError(t, err)
   940  		assert.Equal(t, 9, len(got))
   941  		assert.Equal(t, entities.PageInfo{
   942  			HasNextPage:     true,
   943  			HasPreviousPage: false,
   944  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   945  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o2, 0o0, 17000, time.UTC).Local(),
   946  			}.String()).Encode(),
   947  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   948  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 49, 9000, time.UTC).Local(),
   949  			}.String()).Encode(),
   950  		}, pageInfo)
   951  	})
   952  }
   953  
   954  func TestGetAllMarketData(t *testing.T) {
   955  	ctx := tempTransaction(t)
   956  
   957  	store, err := setupMarketData(t, ctx)
   958  	require.NoError(t, err)
   959  	market := "8cc0e020c0bc2f9eba77749d81ecec8283283b85941722c2cb88318aaf8b8cd8"
   960  	startDate := time.Date(2022, 2, 11, 0, 0, 0, 0, time.UTC)
   961  
   962  	t.Run("should return all results if no cursor pagination is provided", func(t *testing.T) {
   963  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, &startDate, nil, entities.CursorPagination{})
   964  		assert.NoError(t, err)
   965  		assert.Equal(t, 184, len(got))
   966  		wantStartCursor := entities.NewCursor(entities.MarketDataCursor{
   967  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   968  		}.String()).Encode()
   969  		wantEndCursor := entities.NewCursor(entities.MarketDataCursor{
   970  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
   971  		}.String()).Encode()
   972  		assert.Equal(t, entities.PageInfo{
   973  			HasNextPage:     false,
   974  			HasPreviousPage: false,
   975  			StartCursor:     wantStartCursor,
   976  			EndCursor:       wantEndCursor,
   977  		}, pageInfo)
   978  	})
   979  
   980  	t.Run("should return a page of results if cursor pagination is provided with first", func(t *testing.T) {
   981  		first := int32(10)
   982  		pagination, err := entities.NewCursorPagination(&first, nil, nil, nil, false)
   983  		require.NoError(t, err)
   984  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, &startDate, nil, pagination)
   985  		assert.NoError(t, err)
   986  		assert.Equal(t, 10, len(got))
   987  		assert.Equal(t, entities.PageInfo{
   988  			HasNextPage:     true,
   989  			HasPreviousPage: false,
   990  			StartCursor: entities.NewCursor(entities.MarketDataCursor{
   991  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 35, 0, time.UTC).Local(),
   992  			}.String()).Encode(),
   993  			EndCursor: entities.NewCursor(entities.MarketDataCursor{
   994  				SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o1, 49, 9000, time.UTC).Local(),
   995  			}.String()).Encode(),
   996  		}, pageInfo)
   997  	})
   998  
   999  	t.Run("should return the most recent record if no dates and no cursor pagination is provided", func(t *testing.T) {
  1000  		got, pageInfo, err := store.GetHistoricMarketData(ctx, market, nil, nil, entities.CursorPagination{})
  1001  		assert.NoError(t, err)
  1002  		assert.Equal(t, 1, len(got))
  1003  		wantStartCursor := entities.NewCursor(entities.MarketDataCursor{
  1004  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
  1005  		}.String()).Encode()
  1006  		wantEndCursor := entities.NewCursor(entities.MarketDataCursor{
  1007  			SyntheticTime: time.Date(2022, 0o2, 11, 10, 0o5, 41, 183000, time.UTC).Local(),
  1008  		}.String()).Encode()
  1009  		assert.Equal(t, entities.PageInfo{
  1010  			HasNextPage:     true,
  1011  			HasPreviousPage: false,
  1012  			StartCursor:     wantStartCursor,
  1013  			EndCursor:       wantEndCursor,
  1014  		}, pageInfo)
  1015  	})
  1016  }
  1017  
  1018  func setupMarketData(t *testing.T, ctx context.Context) (*sqlstore.MarketData, error) {
  1019  	t.Helper()
  1020  
  1021  	bs := sqlstore.NewBlocks(connectionSource)
  1022  	md := sqlstore.NewMarketData(connectionSource)
  1023  
  1024  	f, err := os.Open(filepath.Join("testdata", "marketdata.csv"))
  1025  	if err != nil {
  1026  		return nil, err
  1027  	}
  1028  
  1029  	defer f.Close()
  1030  
  1031  	reader := csv.NewReader(bufio.NewReader(f))
  1032  
  1033  	var hash []byte
  1034  	hash, err = hex.DecodeString("deadbeef")
  1035  	assert.NoError(t, err)
  1036  
  1037  	addedBlocksAt := make(map[int64]struct{})
  1038  	seqNum := 0
  1039  	for {
  1040  		line, err := reader.Read()
  1041  		if err != nil {
  1042  			if err == io.EOF {
  1043  				break
  1044  			}
  1045  			return nil, err
  1046  		}
  1047  		if len(line) == 0 {
  1048  			continue
  1049  		}
  1050  
  1051  		marketData := csvToMarketData(t, line, seqNum)
  1052  		seqNum++
  1053  
  1054  		if _, alreadyAdded := addedBlocksAt[marketData.VegaTime.UnixNano()]; !alreadyAdded {
  1055  			// Postgres only stores timestamps in microsecond resolution
  1056  			block := entities.Block{
  1057  				VegaTime: marketData.VegaTime,
  1058  				Height:   2,
  1059  				Hash:     hash,
  1060  			}
  1061  
  1062  			// Add it to the database
  1063  			err = bs.Add(ctx, block)
  1064  			require.NoError(t, err)
  1065  			addedBlocksAt[marketData.VegaTime.UnixNano()] = struct{}{}
  1066  		}
  1067  
  1068  		err = md.Add(marketData)
  1069  		require.NoError(t, err)
  1070  	}
  1071  	_, err = md.Flush(ctx)
  1072  	require.NoError(t, err)
  1073  
  1074  	return md, nil
  1075  }
  1076  
  1077  func mustParseDecimal(t *testing.T, value string) decimal.Decimal {
  1078  	t.Helper()
  1079  	d, err := decimal.NewFromString(value)
  1080  	if err != nil {
  1081  		t.Fatalf("could not parse decimal value: %s", err)
  1082  	}
  1083  
  1084  	return d
  1085  }
  1086  
  1087  func mustParseTimestamp(t *testing.T, value string) time.Time {
  1088  	t.Helper()
  1089  	const dbDateFormat = "2006-01-02 15:04:05.999999 -07:00"
  1090  	ts, err := time.Parse(dbDateFormat, value)
  1091  	if err != nil {
  1092  		t.Fatalf("could not parse time: %s", err)
  1093  	}
  1094  
  1095  	return ts
  1096  }
  1097  
  1098  func mustParseUInt64(t *testing.T, value string) uint64 {
  1099  	t.Helper()
  1100  	i, err := strconv.ParseUint(value, 10, 64)
  1101  	if err != nil {
  1102  		t.Fatalf("could not parse int64: %s", err)
  1103  	}
  1104  
  1105  	return i
  1106  }
  1107  
  1108  func mustParseInt64(t *testing.T, value string) int64 {
  1109  	t.Helper()
  1110  	i, err := strconv.ParseInt(value, 10, 64)
  1111  	if err != nil {
  1112  		t.Fatalf("could not parse int64: %s", err)
  1113  	}
  1114  
  1115  	return i
  1116  }
  1117  
  1118  func mustParsePriceMonitoringBounds(t *testing.T, value string) []*vega.PriceMonitoringBounds {
  1119  	t.Helper()
  1120  	if strings.ToLower(value) == "null" {
  1121  		return nil
  1122  	}
  1123  
  1124  	var bounds []*vega.PriceMonitoringBounds
  1125  
  1126  	err := json.Unmarshal([]byte(value), &bounds)
  1127  	if err != nil {
  1128  		t.Fatalf("could not parse Price Monitoring Bounds: %s", err)
  1129  	}
  1130  
  1131  	return bounds
  1132  }
  1133  
  1134  func mustParseLiquidity(t *testing.T, value string) []*vega.LiquidityProviderFeeShare {
  1135  	t.Helper()
  1136  	if strings.ToLower(value) == "null" {
  1137  		return nil
  1138  	}
  1139  
  1140  	var liquidity []*vega.LiquidityProviderFeeShare
  1141  
  1142  	err := json.Unmarshal([]byte(value), &liquidity)
  1143  	if err != nil {
  1144  		t.Fatalf("could not parse Liquidity Provider Fee Share: %s", err)
  1145  	}
  1146  
  1147  	return liquidity
  1148  }
  1149  
  1150  func csvToMarketData(t *testing.T, line []string, seqNum int) *entities.MarketData {
  1151  	t.Helper()
  1152  
  1153  	vegaTime := mustParseTimestamp(t, line[csvColumnVegaTime])
  1154  	syntheticTime := vegaTime.Add(time.Duration(seqNum) * time.Microsecond)
  1155  
  1156  	return &entities.MarketData{
  1157  		MarkPrice:                  mustParseDecimal(t, line[csvColumnMarkPrice]),
  1158  		BestBidPrice:               mustParseDecimal(t, line[csvColumnBestBidPrice]),
  1159  		BestBidVolume:              mustParseUInt64(t, line[csvColumnBestBidVolume]),
  1160  		BestOfferPrice:             mustParseDecimal(t, line[csvColumnBestOfferPrice]),
  1161  		BestOfferVolume:            mustParseUInt64(t, line[csvColumnBestOfferVolume]),
  1162  		BestStaticBidPrice:         mustParseDecimal(t, line[csvColumnBestStaticBidPrice]),
  1163  		BestStaticBidVolume:        mustParseUInt64(t, line[csvColumnBestStaticBidVolume]),
  1164  		BestStaticOfferPrice:       mustParseDecimal(t, line[csvColumnBestStaticOfferPrice]),
  1165  		BestStaticOfferVolume:      mustParseUInt64(t, line[csvColumnBestStaticOfferVolume]),
  1166  		MidPrice:                   mustParseDecimal(t, line[csvColumnMidPrice]),
  1167  		StaticMidPrice:             mustParseDecimal(t, line[csvColumnStaticMidPrice]),
  1168  		Market:                     entities.MarketID(line[csvColumnMarket]),
  1169  		OpenInterest:               mustParseUInt64(t, line[csvColumnOpenInterest]),
  1170  		AuctionEnd:                 mustParseInt64(t, line[csvColumnAuctionEnd]),
  1171  		AuctionStart:               mustParseInt64(t, line[csvColumnAuctionStart]),
  1172  		IndicativePrice:            mustParseDecimal(t, line[csvColumnIndicativePrice]),
  1173  		IndicativeVolume:           mustParseUInt64(t, line[csvColumnIndicativeVolume]),
  1174  		MarketTradingMode:          line[csvColumnMarketTradingMode],
  1175  		AuctionTrigger:             line[csvColumnAuctionTrigger],
  1176  		ExtensionTrigger:           line[csvColumnExtensionTrigger],
  1177  		TargetStake:                mustParseDecimal(t, line[csvColumnTargetStake]),
  1178  		SuppliedStake:              mustParseDecimal(t, line[csvColumnSuppliedStake]),
  1179  		PriceMonitoringBounds:      mustParsePriceMonitoringBounds(t, line[csvColumnPriceMonitoringBounds]),
  1180  		MarketValueProxy:           line[csvColumnMarketValueProxy],
  1181  		LiquidityProviderFeeShares: mustParseLiquidity(t, line[csvColumnLiquidityProviderFeeShares]),
  1182  		MarketState:                line[csvColumnMarketState],
  1183  		VegaTime:                   vegaTime,
  1184  		SeqNum:                     uint64(seqNum),
  1185  		SyntheticTime:              syntheticTime,
  1186  		MarketGrowth:               mustParseDecimal(t, line[csvColumnMarketGrowth]),
  1187  		LastTradedPrice:            mustParseDecimal(t, line[csvColumnLastTradedPrice]),
  1188  		ProductData: &entities.ProductData{
  1189  			ProductData: &vega.ProductData{
  1190  				Data: &vega.ProductData_PerpetualData{
  1191  					PerpetualData: &vega.PerpetualData{
  1192  						InternalCompositePrice:         "100",
  1193  						NextInternalCompositePriceCalc: 200,
  1194  						InternalCompositePriceType:     vega.CompositePriceType_COMPOSITE_PRICE_TYPE_LAST_TRADE,
  1195  					},
  1196  				},
  1197  			},
  1198  		},
  1199  		MarkPriceType: "COMPOSITE_PRICE_TYPE_LAST_TRADE",
  1200  	}
  1201  }