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 }