code.vegaprotocol.io/vega@v0.79.0/datanode/service/market_depth_amm_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 service_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/datanode/entities"
    25  	"code.vegaprotocol.io/vega/datanode/service"
    26  	"code.vegaprotocol.io/vega/datanode/service/mocks"
    27  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/libs/ptr"
    30  	"code.vegaprotocol.io/vega/logging"
    31  	"code.vegaprotocol.io/vega/protos/vega"
    32  
    33  	"github.com/golang/mock/gomock"
    34  	"github.com/shopspring/decimal"
    35  	"github.com/stretchr/testify/assert"
    36  )
    37  
    38  func getService(t *testing.T) *MDS {
    39  	t.Helper()
    40  	cfg := service.MarketDepthConfig{
    41  		AmmFullExpansionPercentage: 1,
    42  		AmmMaxEstimatedSteps:       5,
    43  		AmmEstimatedStepPercentage: 0.2,
    44  	}
    45  	return getServiceWithConfig(t, cfg)
    46  }
    47  
    48  func getServiceWithConfig(t *testing.T, cfg service.MarketDepthConfig) *MDS {
    49  	t.Helper()
    50  	ctrl := gomock.NewController(t)
    51  	pos := mocks.NewMockPositionStore(ctrl)
    52  	orders := mocks.NewMockOrderStore(ctrl)
    53  	marketData := mocks.NewMockMarketDataStore(ctrl)
    54  	amm := mocks.NewMockAMMStore(ctrl)
    55  	markets := mocks.NewMockMarketStore(ctrl)
    56  	assets := mocks.NewMockAssetStore(ctrl)
    57  
    58  	return &MDS{
    59  		service:    service.NewMarketDepth(cfg, orders, amm, marketData, pos, assets, markets, logging.NewTestLogger()),
    60  		ctrl:       ctrl,
    61  		pos:        pos,
    62  		amm:        amm,
    63  		orders:     orders,
    64  		marketData: marketData,
    65  		markets:    markets,
    66  		assets:     assets,
    67  	}
    68  }
    69  
    70  func Test_0015_NP_OBES_002(t *testing.T) {
    71  	/*
    72  		0015-NP-OBES-002:
    73  			With amm_full_expansion_percentage set to 3%, amm_estimate_step_percentage set to 5% and amm_max_estimated_steps set to 2, when the mid-price is 100 then the order book expansion should return:
    74  
    75  			    Volume levels at every valid tick between 97 and 103
    76  			    Volume levels outside that at every 1 increment from 108 to 116 and 92 to 87
    77  			    No volume levels above 116 or below 87
    78  	*/
    79  	ctx := context.Background()
    80  	mds := getServiceWithConfig(t,
    81  		service.MarketDepthConfig{
    82  			AmmFullExpansionPercentage: 3,
    83  			AmmEstimatedStepPercentage: 5,
    84  			AmmMaxEstimatedSteps:       2,
    85  		},
    86  	)
    87  	defer mds.ctrl.Finish()
    88  
    89  	marketID := vgcrypto.RandomHash()
    90  
    91  	mds.orders.EXPECT().GetLiveOrders(gomock.Any()).Return([]entities.Order{}, nil)
    92  	ensureDecimalPlaces(t, mds, 1, 1)
    93  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
    94  
    95  	// mid-price is 100
    96  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(100)}, nil)
    97  
    98  	// data node is starting from network history, initialise market-depth based on whats aleady there
    99  	pool := getAMMDefinitionMid100(t, marketID)
   100  	mds.amm.EXPECT().ListActive(gomock.Any()).Return([]entities.AMMPool{pool}, nil).Times(1)
   101  	mds.service.Initialise(ctx)
   102  
   103  	// buys estimates at 87, 92, accurate ones at  97, 98, 99
   104  	prices := map[uint64]bool{
   105  		87: true,
   106  		92: true,
   107  		97: false,
   108  		98: false,
   109  		99: false,
   110  	}
   111  	assert.Equal(t, 5, mds.service.GetBuyPriceLevels(marketID))
   112  	for p, estimated := range prices {
   113  		volume := mds.service.GetVolumeAtPrice(marketID, types.SideBuy, p)
   114  		if estimated {
   115  			volume = mds.service.GetEstimatedVolumeAtPrice(marketID, types.SideBuy, p)
   116  		}
   117  		assert.NotEqual(t, uint64(0), volume)
   118  	}
   119  
   120  	// sell estimates at 109, 104, accurate ones at  103, 102, 101
   121  	prices = map[uint64]bool{
   122  		109: true,
   123  		104: true,
   124  		103: false,
   125  		102: false,
   126  		101: false,
   127  	}
   128  	assert.Equal(t, 5, mds.service.GetSellPriceLevels(marketID))
   129  	for p, estimated := range prices {
   130  		volume := mds.service.GetVolumeAtPrice(marketID, types.SideSell, p)
   131  		if estimated {
   132  			volume = mds.service.GetEstimatedVolumeAtPrice(marketID, types.SideSell, p)
   133  		}
   134  		assert.NotEqual(t, uint64(0), volume)
   135  	}
   136  }
   137  
   138  func TestAMMMarketDepth(t *testing.T) {
   139  	ctx := context.Background()
   140  	mds := getService(t)
   141  	defer mds.ctrl.Finish()
   142  
   143  	marketID := vgcrypto.RandomHash()
   144  
   145  	ensureLiveOrders(t, mds, marketID)
   146  	ensureDecimalPlaces(t, mds, 1, 1)
   147  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   148  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   149  
   150  	// data node is starting from network history, initialise market-depth based on whats aleady there
   151  	pool := ensureAMMs(t, mds, marketID)
   152  	mds.service.Initialise(ctx)
   153  
   154  	assert.Equal(t, 240, int(mds.service.GetTotalAMMVolume(marketID)))
   155  	assert.Equal(t, 120, int(mds.service.GetAMMVolume(marketID, true)))
   156  	assert.Equal(t, 120, int(mds.service.GetAMMVolume(marketID, false)))
   157  	assert.Equal(t, 260, int(mds.service.GetTotalVolume(marketID)))
   158  
   159  	assert.Equal(t, "1999", mds.service.GetBestBidPrice(marketID).String())
   160  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(marketID).String())
   161  
   162  	// now pretend that something traded with the AMM and its position is now 10 long
   163  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 10}, nil)
   164  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   165  	mds.service.AddOrder(
   166  		&types.Order{
   167  			ID:       vgcrypto.RandomHash(),
   168  			Party:    pool.AmmPartyID.String(),
   169  			MarketID: marketID,
   170  			Side:     types.SideBuy,
   171  			Status:   entities.OrderStatusFilled,
   172  		},
   173  		time.Date(2022, 3, 8, 16, 15, 39, 901022000, time.UTC),
   174  		37,
   175  	)
   176  
   177  	// volume should be the same but the buys and sells should have shifted
   178  	assert.Equal(t, 240, int(mds.service.GetTotalAMMVolume(marketID)))
   179  	assert.Equal(t, 120, int(mds.service.GetAMMVolume(marketID, true)))
   180  	assert.Equal(t, 120, int(mds.service.GetAMMVolume(marketID, false)))
   181  	assert.Equal(t, 260, int(mds.service.GetTotalVolume(marketID)))
   182  
   183  	assert.Equal(t, "1995", mds.service.GetBestBidPrice(marketID).String())
   184  	assert.Equal(t, "1998", mds.service.GetBestAskPrice(marketID).String())
   185  
   186  	// now the AMM is updated so that its definition has changed, namely that its curve when short is removed
   187  	pool.ParametersUpperBound = nil
   188  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 10}, nil)
   189  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   190  	mds.service.OnAMMUpdate(pool, time.Now(), 999)
   191  
   192  	// volume should change
   193  	assert.Equal(t, 125, int(mds.service.GetTotalAMMVolume(marketID)))
   194  	assert.Equal(t, 65, int(mds.service.GetAMMVolume(marketID, true)))
   195  	assert.Equal(t, 60, int(mds.service.GetAMMVolume(marketID, false)))
   196  	assert.Equal(t, 145, int(mds.service.GetTotalVolume(marketID)))
   197  	assert.Equal(t, "1995", mds.service.GetBestBidPrice(marketID).String())
   198  	assert.Equal(t, "1998", mds.service.GetBestAskPrice(marketID).String())
   199  
   200  	// and there should definitely be no volume at 2001
   201  	assert.Equal(t, 0, int(mds.service.GetVolumeAtPrice(marketID, types.SideSell, 2001)))
   202  
   203  	// now the AMM is cancelled, we expect all AMM volume to be removed
   204  	pool.Status = entities.AMMStatusCancelled
   205  	mds.service.OnAMMUpdate(pool, time.Now(), 1000)
   206  
   207  	assert.Equal(t, 0, int(mds.service.GetTotalAMMVolume(marketID)))
   208  	assert.Equal(t, 20, int(mds.service.GetTotalVolume(marketID)))
   209  }
   210  
   211  func TestAMMSparseMarketDepth(t *testing.T) {
   212  	ctx := context.Background()
   213  	mds := getService(t)
   214  	defer mds.ctrl.Finish()
   215  
   216  	marketID := vgcrypto.RandomHash()
   217  
   218  	ensureLiveOrders(t, mds, marketID)
   219  	ensureDecimalPlaces(t, mds, 1, 1)
   220  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   221  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   222  
   223  	pool := getSparseAMMDefinition(t, marketID)
   224  	mds.amm.EXPECT().ListActive(gomock.Any()).Return([]entities.AMMPool{pool}, nil).Times(1)
   225  	mds.service.Initialise(ctx)
   226  
   227  	// little volume over the range, and its all estimated
   228  	assert.Equal(t, 2, int(mds.service.GetTotalAMMVolume(marketID)))
   229  	assert.Equal(t, 2, int(mds.service.GetAMMVolume(marketID, true)))
   230  	assert.Equal(t, 0, int(mds.service.GetAMMVolume(marketID, false)))
   231  	assert.Equal(t, 22, int(mds.service.GetTotalVolume(marketID)))
   232  
   233  	// best bid and best ask
   234  	assert.Equal(t, "1960", mds.service.GetBestBidPrice(marketID).String())
   235  	assert.Equal(t, "2033", mds.service.GetBestAskPrice(marketID).String())
   236  }
   237  
   238  func TestAMMInitialiseNoAMM(t *testing.T) {
   239  	ctx := context.Background()
   240  	mds := getService(t)
   241  	defer mds.ctrl.Finish()
   242  
   243  	marketID := vgcrypto.RandomHash()
   244  
   245  	ensureLiveOrders(t, mds, marketID)
   246  
   247  	// initialise when there are no AMMs
   248  	mds.amm.EXPECT().ListActive(gomock.Any()).Return(nil, nil).Times(1)
   249  	mds.service.Initialise(ctx)
   250  	assert.Equal(t, 0, int(mds.service.GetTotalAMMVolume(marketID)))
   251  	assert.Equal(t, 20, int(mds.service.GetTotalVolume(marketID)))
   252  
   253  	// now a new AMM for a new market appears
   254  	newMarket := vgcrypto.RandomHash()
   255  	pool := getAMMDefinition(t, newMarket)
   256  
   257  	ensureDecimalPlaces(t, mds, 1, 1)
   258  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   259  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   260  	mds.service.OnAMMUpdate(pool, time.Now(), 1000)
   261  
   262  	// check it makes sense
   263  	assert.Equal(t, 240, int(mds.service.GetTotalAMMVolume(newMarket)))
   264  	assert.Equal(t, "1999", mds.service.GetBestBidPrice(newMarket).String())
   265  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(newMarket).String())
   266  }
   267  
   268  func TestAMMStepOverFairPrice(t *testing.T) {
   269  	ctx := context.Background()
   270  	mds := getService(t)
   271  	defer mds.ctrl.Finish()
   272  
   273  	// this is for an awkward case where an AMM's position exists between the position of two ticks
   274  	// for example if an AMM's base is at 2000, and it has 5 volume between 2000 -> 2001 our accurate
   275  	// expansion will step from 2000 -> 2001 and say there is 5 SELL volume at price 2001.
   276  	//
   277  	// Say the AMM now trades 1, when we expand and step from 2000 -> 2001 there should be only 4 SELL volume
   278  	// at 2001 and now 1 BUY volume at 1999
   279  
   280  	marketID := vgcrypto.RandomHash()
   281  	ensureLiveOrders(t, mds, marketID)
   282  	ensureDecimalPlaces(t, mds, 1, 1)
   283  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   284  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   285  
   286  	// data node is starting from network history, initialise market-depth based on whats aleady there
   287  	pool := ensureAMMs(t, mds, marketID)
   288  	mds.service.Initialise(ctx)
   289  
   290  	assert.Equal(t, "1999", mds.service.GetBestBidPrice(marketID).String())
   291  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(marketID).String())
   292  	assert.Equal(t, 3, int(mds.service.GetVolumeAtPrice(marketID, types.SideBuy, 1999)))
   293  	assert.Equal(t, 3, int(mds.service.GetVolumeAtPrice(marketID, types.SideSell, 2001)))
   294  
   295  	// now a single trade happens making the AMM 1 short
   296  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 1}, nil)
   297  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   298  	mds.service.AddOrder(
   299  		&types.Order{
   300  			ID:       vgcrypto.RandomHash(),
   301  			Party:    pool.AmmPartyID.String(),
   302  			MarketID: marketID,
   303  			Side:     types.SideBuy,
   304  			Status:   entities.OrderStatusFilled,
   305  		},
   306  		time.Date(2022, 3, 8, 16, 15, 39, 901022000, time.UTC),
   307  		37,
   308  	)
   309  
   310  	assert.Equal(t, "1998", mds.service.GetBestBidPrice(marketID).String())
   311  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(marketID).String())
   312  	assert.Equal(t, 0, int(mds.service.GetVolumeAtPrice(marketID, types.SideBuy, 1999)))
   313  	assert.Equal(t, 0, int(mds.service.GetVolumeAtPrice(marketID, types.SideSell, 2000)))
   314  	assert.Equal(t, 5, int(mds.service.GetVolumeAtPrice(marketID, types.SideBuy, 1998)))
   315  	assert.Equal(t, 4, int(mds.service.GetVolumeAtPrice(marketID, types.SideSell, 2001)))
   316  }
   317  
   318  func TestAMMSmallBounds(t *testing.T) {
   319  	ctx := context.Background()
   320  	mds := getServiceWithConfig(t,
   321  		service.MarketDepthConfig{
   322  			AmmFullExpansionPercentage: 0.000001,
   323  			AmmEstimatedStepPercentage: 0.000001,
   324  			AmmMaxEstimatedSteps:       2,
   325  		},
   326  	)
   327  	defer mds.ctrl.Finish()
   328  
   329  	marketID := vgcrypto.RandomHash()
   330  	ensureLiveOrders(t, mds, marketID)
   331  	ensureDecimalPlaces(t, mds, 1, 1)
   332  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   333  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   334  
   335  	// data node is starting from network history, initialise market-depth based on whats aleady there
   336  	ensureAMMs(t, mds, marketID)
   337  	mds.service.Initialise(ctx)
   338  
   339  	assert.Equal(t, "1999", mds.service.GetBestBidPrice(marketID).String())
   340  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(marketID).String())
   341  	assert.Equal(t, 3, int(mds.service.GetVolumeAtPrice(marketID, types.SideBuy, 1999)))
   342  	assert.Equal(t, 3, int(mds.service.GetVolumeAtPrice(marketID, types.SideSell, 2001)))
   343  
   344  	// anywhere else is zero
   345  	assert.Equal(t, 0, int(mds.service.GetVolumeAtPrice(marketID, types.SideBuy, 1998)))
   346  	assert.Equal(t, 0, int(mds.service.GetVolumeAtPrice(marketID, types.SideSell, 2002)))
   347  }
   348  
   349  func TestEstimatedStepOverAMMBound(t *testing.T) {
   350  	ctx := context.Background()
   351  	mds := getServiceWithConfig(t,
   352  		service.MarketDepthConfig{
   353  			AmmFullExpansionPercentage: 5,
   354  			AmmEstimatedStepPercentage: 7.6, // make this a werid number so our estimated steps are not nice multiplies of 10
   355  			AmmMaxEstimatedSteps:       5,
   356  		},
   357  	)
   358  	defer mds.ctrl.Finish()
   359  
   360  	marketID := vgcrypto.RandomHash()
   361  	ensureLiveOrders(t, mds, marketID)
   362  	ensureDecimalPlaces(t, mds, 1, 1)
   363  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   364  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   365  
   366  	// data node is starting from network history, initialise market-depth based on whats aleady there
   367  	ensureAMMs(t, mds, marketID)
   368  	mds.service.Initialise(ctx)
   369  
   370  	assert.Equal(t, "1999", mds.service.GetBestBidPrice(marketID).String())
   371  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(marketID).String())
   372  	assert.Equal(t, 3, int(mds.service.GetVolumeAtPrice(marketID, types.SideBuy, 1999)))
   373  	assert.Equal(t, 3, int(mds.service.GetVolumeAtPrice(marketID, types.SideSell, 2001)))
   374  }
   375  
   376  func TestExpansionMuchBiggerThanAMMs(t *testing.T) {
   377  	ctx := context.Background()
   378  
   379  	cfg := service.MarketDepthConfig{
   380  		AmmFullExpansionPercentage: 1,
   381  		AmmMaxEstimatedSteps:       10,
   382  		AmmEstimatedStepPercentage: 5,
   383  	}
   384  
   385  	mds := getServiceWithConfig(t, cfg)
   386  	defer mds.ctrl.Finish()
   387  
   388  	marketID := vgcrypto.RandomHash()
   389  
   390  	ensureLiveOrders(t, mds, marketID)
   391  	ensureDecimalPlaces(t, mds, 1, 1)
   392  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   393  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   394  
   395  	// data node is starting from network history, initialise market-depth based on whats aleady there
   396  	ensureAMMs(t, mds, marketID)
   397  	mds.service.Initialise(ctx)
   398  
   399  	assert.Equal(t, 465, int(mds.service.GetTotalAMMVolume(marketID)))
   400  	assert.Equal(t, 345, int(mds.service.GetAMMVolume(marketID, true)))
   401  	assert.Equal(t, 120, int(mds.service.GetAMMVolume(marketID, false)))
   402  	assert.Equal(t, 485, int(mds.service.GetTotalVolume(marketID)))
   403  
   404  	assert.Equal(t, "1999", mds.service.GetBestBidPrice(marketID).String())
   405  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(marketID).String())
   406  }
   407  
   408  func TestMidPriceMove(t *testing.T) {
   409  	ctx := context.Background()
   410  
   411  	mds := getService(t)
   412  	defer mds.ctrl.Finish()
   413  
   414  	marketID := vgcrypto.RandomHash()
   415  
   416  	ensureLiveOrders(t, mds, marketID)
   417  	ensureDecimalPlaces(t, mds, 1, 1)
   418  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 0}, nil)
   419  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(2000)}, nil)
   420  
   421  	// data node is starting from network history, initialise market-depth based on whats aleady there
   422  	pool := ensureAMMs(t, mds, marketID)
   423  	mds.service.Initialise(ctx)
   424  
   425  	assert.Equal(t, 240, int(mds.service.GetTotalAMMVolume(marketID)))
   426  	assert.Equal(t, 120, int(mds.service.GetAMMVolume(marketID, true)))
   427  	assert.Equal(t, 120, int(mds.service.GetAMMVolume(marketID, false)))
   428  	assert.Equal(t, 260, int(mds.service.GetTotalVolume(marketID)))
   429  
   430  	assert.Equal(t, "1999", mds.service.GetBestBidPrice(marketID).String())
   431  	assert.Equal(t, "2001", mds.service.GetBestAskPrice(marketID).String())
   432  
   433  	// now say the mid-price moves a little, we want to check we recalculate the levels properly
   434  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: 500}, nil)
   435  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(1800)}, nil)
   436  	mds.service.AddOrder(
   437  		&types.Order{
   438  			ID:       vgcrypto.RandomHash(),
   439  			Party:    pool.AmmPartyID.String(),
   440  			MarketID: marketID,
   441  			Side:     types.SideBuy,
   442  			Status:   entities.OrderStatusFilled,
   443  		},
   444  		time.Date(2022, 3, 8, 16, 15, 39, 901022000, time.UTC),
   445  		37,
   446  	)
   447  
   448  	assert.Equal(t, "1828", mds.service.GetBestBidPrice(marketID).String())
   449  	assert.Equal(t, "3000", mds.service.GetBestAskPrice(marketID).String()) // this is an actual order volume not AMM volume
   450  }
   451  
   452  func TestFairgroundAMM(t *testing.T) {
   453  	ctx := context.Background()
   454  
   455  	mds := getService(t)
   456  	defer mds.ctrl.Finish()
   457  
   458  	marketID := vgcrypto.RandomHash()
   459  
   460  	mds.orders.EXPECT().GetLiveOrders(gomock.Any()).Return(nil, nil)
   461  	ensureDecimalPlaces(t, mds, 9, 5)
   462  	mds.pos.EXPECT().GetByMarketAndParty(gomock.Any(), gomock.Any(), gomock.Any()).Return(entities.Position{OpenVolume: -69005905}, nil)
   463  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(12955)}, nil)
   464  
   465  	pool := getAMMDefinitionTestnet(t, marketID)
   466  	mds.amm.EXPECT().ListActive(gomock.Any()).Return([]entities.AMMPool{pool}, nil).Times(1)
   467  	mds.service.Initialise(ctx)
   468  
   469  	// AMM's fair price is 129543034, so +/- one each side is 129533034, 129553034
   470  	// the we round *away* from the fair price and get:
   471  	assert.Equal(t, "12953", mds.service.GetBestBidPrice(marketID).String())
   472  	assert.Equal(t, "12956", mds.service.GetBestAskPrice(marketID).String())
   473  }
   474  
   475  func TestAMMDepthOutOfRange(t *testing.T) {
   476  	ctx := context.Background()
   477  
   478  	cfg := service.MarketDepthConfig{
   479  		AmmFullExpansionPercentage: 200,
   480  		AmmEstimatedStepPercentage: 0,
   481  		AmmMaxEstimatedSteps:       0,
   482  	}
   483  	mds := getServiceWithConfig(t, cfg)
   484  	defer mds.ctrl.Finish()
   485  
   486  	marketID := vgcrypto.RandomHash()
   487  
   488  	ensureLiveOrders(t, mds, marketID)
   489  	ensureDecimalPlaces(t, mds, 1, 1)
   490  
   491  	// set the mid-price to a value no where near the AMM's prices
   492  	mds.marketData.EXPECT().GetMarketDataByID(gomock.Any(), gomock.Any()).Times(1).Return(entities.MarketData{MidPrice: num.DecimalFromInt64(10)}, nil)
   493  
   494  	// data node is starting from network history, initialise market-depth based on whats aleady there
   495  	ensureAMMs(t, mds, marketID)
   496  	mds.service.Initialise(ctx)
   497  
   498  	assert.Equal(t, 0, int(mds.service.GetTotalAMMVolume(marketID)))
   499  	assert.Equal(t, 0, int(mds.service.GetAMMVolume(marketID, true)))
   500  	assert.Equal(t, 0, int(mds.service.GetAMMVolume(marketID, false)))
   501  }
   502  
   503  func ensureLiveOrders(t *testing.T, mds *MDS, marketID string) {
   504  	t.Helper()
   505  	mds.orders.EXPECT().GetLiveOrders(gomock.Any()).Return([]entities.Order{
   506  		{
   507  			ID:        entities.OrderID(vgcrypto.RandomHash()),
   508  			MarketID:  entities.MarketID(marketID),
   509  			PartyID:   entities.PartyID(vgcrypto.RandomHash()),
   510  			Side:      types.SideBuy,
   511  			Price:     decimal.NewFromInt(1000),
   512  			Size:      10,
   513  			Remaining: 10,
   514  			Type:      entities.OrderTypeLimit,
   515  			Status:    entities.OrderStatusActive,
   516  			VegaTime:  time.Date(2022, 3, 8, 14, 14, 45, 762739000, time.UTC),
   517  			SeqNum:    32,
   518  		},
   519  		{
   520  			ID:        entities.OrderID(vgcrypto.RandomHash()),
   521  			MarketID:  entities.MarketID(marketID),
   522  			PartyID:   entities.PartyID(vgcrypto.RandomHash()),
   523  			Side:      types.SideSell,
   524  			Type:      entities.OrderTypeLimit,
   525  			Status:    entities.OrderStatusActive,
   526  			Price:     decimal.NewFromInt(3000),
   527  			Size:      10,
   528  			Remaining: 10,
   529  			VegaTime:  time.Date(2022, 3, 8, 14, 15, 39, 901022000, time.UTC),
   530  			SeqNum:    33,
   531  		},
   532  	}, nil).Times(1)
   533  }
   534  
   535  func getSparseAMMDefinition(t *testing.T, marketID string) entities.AMMPool {
   536  	t.Helper()
   537  	return entities.AMMPool{
   538  		PartyID:                  entities.PartyID(vgcrypto.RandomHash()),
   539  		AmmPartyID:               entities.PartyID(vgcrypto.RandomHash()),
   540  		MarketID:                 entities.MarketID(marketID),
   541  		ParametersLowerBound:     ptr.From(num.DecimalFromInt64(1800)),
   542  		LowerVirtualLiquidity:    num.DecimalFromFloat(5807.2351752738390703940959525483259),
   543  		LowerTheoreticalPosition: num.DecimalFromFloat(7.024119613637249),
   544  		ParametersBase:           num.DecimalFromInt64(2000),
   545  		ParametersUpperBound:     ptr.From(num.DecimalFromInt64(2200)),
   546  		UpperVirtualLiquidity:    num.DecimalFromFloat(6106.0011747584543685842512031629329),
   547  		UpperTheoreticalPosition: num.DecimalFromFloat(6.3539545218646371),
   548  	}
   549  }
   550  
   551  func getAMMDefinition(t *testing.T, marketID string) entities.AMMPool {
   552  	t.Helper()
   553  	return entities.AMMPool{
   554  		PartyID:                  entities.PartyID(vgcrypto.RandomHash()),
   555  		AmmPartyID:               entities.PartyID(vgcrypto.RandomHash()),
   556  		MarketID:                 entities.MarketID(marketID),
   557  		ParametersLowerBound:     ptr.From(num.DecimalFromInt64(1800)),
   558  		LowerVirtualLiquidity:    num.DecimalFromFloat(580723.51752738390596462639919437474617),
   559  		LowerTheoreticalPosition: num.DecimalFromFloat(702.4119613637248987),
   560  		ParametersBase:           num.DecimalFromInt64(2000),
   561  		ParametersUpperBound:     ptr.From(num.DecimalFromInt64(2200)),
   562  		UpperVirtualLiquidity:    num.DecimalFromFloat(610600.1174758454383959875699679680084),
   563  		UpperTheoreticalPosition: num.DecimalFromFloat(635.3954521864637116),
   564  	}
   565  }
   566  
   567  func getAMMDefinitionMid100(t *testing.T, marketID string) entities.AMMPool {
   568  	t.Helper()
   569  	return entities.AMMPool{
   570  		PartyID:                  entities.PartyID(vgcrypto.RandomHash()),
   571  		AmmPartyID:               entities.PartyID(vgcrypto.RandomHash()),
   572  		MarketID:                 entities.MarketID(marketID),
   573  		ParametersLowerBound:     ptr.From(num.DecimalFromInt64(50)),
   574  		LowerVirtualLiquidity:    num.DecimalFromFloat(109933.47060272754448304259594317590451),
   575  		LowerTheoreticalPosition: num.DecimalFromFloat(4553.5934482393695541),
   576  		ParametersBase:           num.DecimalFromInt64(100),
   577  		ParametersUpperBound:     ptr.From(num.DecimalFromInt64(150)),
   578  		UpperVirtualLiquidity:    num.DecimalFromFloat(174241.4190625882586702427011885011744),
   579  		UpperTheoreticalPosition: num.DecimalFromFloat(3197.389614198983918),
   580  	}
   581  }
   582  
   583  func getAMMDefinitionTestnet(t *testing.T, marketID string) entities.AMMPool {
   584  	t.Helper()
   585  
   586  	// position -69005905
   587  
   588  	return entities.AMMPool{
   589  		PartyID:                  entities.PartyID(vgcrypto.RandomHash()),
   590  		AmmPartyID:               entities.PartyID(vgcrypto.RandomHash()),
   591  		MarketID:                 entities.MarketID(marketID),
   592  		ParametersLowerBound:     ptr.From(num.DecimalFromInt64(11403)),
   593  		LowerVirtualLiquidity:    num.DecimalFromFloat(32934372037780.849503179454583540865465761125),
   594  		LowerTheoreticalPosition: num.DecimalFromFloat(158269985.323671339473934),
   595  		ParametersBase:           num.DecimalFromInt64(12670),
   596  		ParametersUpperBound:     ptr.From(num.DecimalFromInt64(13937)),
   597  		UpperVirtualLiquidity:    num.DecimalFromFloat(70393727154384.2551793351731482811200266360637),
   598  		UpperTheoreticalPosition: num.DecimalFromFloat(291036775.097792633711267),
   599  	}
   600  }
   601  
   602  func ensureAMMs(t *testing.T, mds *MDS, marketID string) entities.AMMPool {
   603  	t.Helper()
   604  
   605  	pool := getAMMDefinition(t, marketID)
   606  	mds.amm.EXPECT().ListActive(gomock.Any()).Return([]entities.AMMPool{pool}, nil).Times(1)
   607  	return pool
   608  }
   609  
   610  func ensureDecimalPlaces(t *testing.T, mds *MDS, adp, mdp int) {
   611  	t.Helper()
   612  
   613  	market := entities.Market{
   614  		TradableInstrument: entities.TradableInstrument{
   615  			TradableInstrument: &vega.TradableInstrument{
   616  				Instrument: &vega.Instrument{
   617  					Product: &vega.Instrument_Future{
   618  						Future: &vega.Future{},
   619  					},
   620  				},
   621  			},
   622  		},
   623  		DecimalPlaces: mdp,
   624  		TickSize:      ptr.From(num.DecimalOne()),
   625  	}
   626  	mds.markets.EXPECT().GetByID(gomock.Any(), gomock.Any()).Return(market, nil)
   627  
   628  	asset := entities.Asset{
   629  		Decimals: adp,
   630  	}
   631  	mds.assets.EXPECT().GetByID(gomock.Any(), gomock.Any()).Return(asset, nil)
   632  }