code.vegaprotocol.io/vega@v0.79.0/core/positions/engine_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 positions_test
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"math"
    22  	"testing"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/core/integration/stubs"
    27  	"code.vegaprotocol.io/vega/core/positions"
    28  	"code.vegaprotocol.io/vega/core/types"
    29  	"code.vegaprotocol.io/vega/libs/num"
    30  	"code.vegaprotocol.io/vega/logging"
    31  
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  func TestUpdatePosition(t *testing.T) {
    37  	t.Run("Update position regular", testUpdatePositionRegular)
    38  }
    39  
    40  func TestRegisterLargeOrder(t *testing.T) {
    41  	const (
    42  		buysize  int64 = 123
    43  		sellsize int64 = 456
    44  	)
    45  	ctx := context.Background()
    46  	e := getTestEngine(t)
    47  	orderBuy := types.Order{
    48  		Party:     "test_party",
    49  		Side:      types.SideBuy,
    50  		Size:      uint64(buysize),
    51  		Remaining: uint64(buysize),
    52  		Price:     num.UintZero(),
    53  	}
    54  
    55  	assert.NoError(t, e.ValidateOrder(&orderBuy))
    56  	_ = e.RegisterOrder(ctx, &orderBuy)
    57  
    58  	// now say we're going to do another MASSIVE one
    59  	orderBuy2 := types.Order{
    60  		Party:     "test_party",
    61  		Side:      types.SideBuy,
    62  		Size:      math.MaxInt64,
    63  		Remaining: math.MaxInt64,
    64  		Price:     num.UintZero(),
    65  	}
    66  	assert.Error(t, e.ValidateOrder(&orderBuy2)) // should fail because if we registered it'll overflow
    67  	require.Panics(t, func() {
    68  		e.RegisterOrder(ctx, &orderBuy2) // should panic and not silently overflow
    69  	})
    70  }
    71  
    72  func TestAmendLargeOrder(t *testing.T) {
    73  	const (
    74  		buysize  int64 = 123
    75  		sellsize int64 = 456
    76  	)
    77  	ctx := context.Background()
    78  	e := getTestEngine(t)
    79  	orderBuy := types.Order{
    80  		Party:     "test_party",
    81  		Side:      types.SideBuy,
    82  		Size:      uint64(buysize),
    83  		Remaining: uint64(buysize),
    84  		Price:     num.UintZero(),
    85  	}
    86  
    87  	assert.NoError(t, e.ValidateOrder(&orderBuy))
    88  	_ = e.RegisterOrder(ctx, &orderBuy)
    89  
    90  	// now say we're going to do an amend to a massive one
    91  	orderBuy2 := types.Order{
    92  		Party:     "test_party",
    93  		Side:      types.SideBuy,
    94  		Size:      math.MaxUint64,
    95  		Remaining: math.MaxUint64,
    96  		Price:     num.UintZero(),
    97  	}
    98  	assert.Error(t, e.ValidateAmendOrder(&orderBuy, &orderBuy2)) // should fail because if we registered it'll overflow
    99  	require.Panics(t, func() {
   100  		e.RegisterOrder(ctx, &orderBuy2) // should panic and not silently overflow
   101  	})
   102  }
   103  
   104  func TestGetOpenInterest(t *testing.T) {
   105  	engine := getTestEngine(t)
   106  	assert.Empty(t, engine.Positions())
   107  	var (
   108  		buyer         = "buyer_id"
   109  		buyer2        = "buyer_id2"
   110  		seller        = "seller_id"
   111  		size   uint64 = 10
   112  		price         = num.NewUint(10000)
   113  	)
   114  	passive1 := &types.Order{
   115  		Party:     buyer,
   116  		Remaining: size,
   117  		Price:     price,
   118  		Side:      types.SideBuy,
   119  	}
   120  	passive2 := &types.Order{
   121  		Party:     buyer2,
   122  		Remaining: size,
   123  		Price:     price,
   124  		Side:      types.SideBuy,
   125  	}
   126  	aggressive := &types.Order{
   127  		Party:     seller,
   128  		Remaining: size * 2,
   129  		Price:     price,
   130  		Side:      types.SideSell,
   131  	}
   132  	engine.RegisterOrder(context.TODO(), passive1)
   133  	engine.RegisterOrder(context.TODO(), passive2)
   134  	engine.RegisterOrder(context.TODO(), aggressive)
   135  
   136  	trade := types.Trade{
   137  		Type:      types.TradeTypeDefault,
   138  		ID:        "trade_id",
   139  		MarketID:  "market_id",
   140  		Price:     num.NewUint(10000),
   141  		Size:      size,
   142  		Buyer:     buyer,
   143  		Seller:    seller,
   144  		BuyOrder:  "buy_order_id",
   145  		SellOrder: "sell_order_id",
   146  		Timestamp: time.Now().Unix(),
   147  	}
   148  	_ = engine.Update(context.Background(), &trade, passive1, aggressive)
   149  	trade = types.Trade{
   150  		Type:      types.TradeTypeDefault,
   151  		ID:        "trade_id",
   152  		MarketID:  "market_id",
   153  		Price:     num.NewUint(10000),
   154  		Size:      size,
   155  		Buyer:     buyer2,
   156  		Seller:    seller,
   157  		BuyOrder:  "buy_order_id",
   158  		SellOrder: "sell_order_id",
   159  		Timestamp: time.Now().Unix(),
   160  	}
   161  	_ = engine.Update(context.Background(), &trade, passive2, aggressive)
   162  	// 3 positions
   163  	// 2 at + 10
   164  	// 1 at -20
   165  	// we should get an open interest of 20
   166  	openInterest := engine.GetOpenInterest()
   167  	assert.Equal(t, 20, int(openInterest))
   168  }
   169  
   170  func testUpdatePositionRegular(t *testing.T) {
   171  	engine := getTestEngine(t)
   172  	assert.Empty(t, engine.Positions())
   173  	var (
   174  		buyer         = "buyer_id"
   175  		seller        = "seller_id"
   176  		size   uint64 = 10
   177  		price         = num.NewUint(10000)
   178  	)
   179  	passive := &types.Order{
   180  		Party:     buyer,
   181  		Remaining: size,
   182  		Price:     price,
   183  		Side:      types.SideBuy,
   184  	}
   185  	aggressive := &types.Order{
   186  		Party:     seller,
   187  		Remaining: size,
   188  		Price:     price,
   189  		Side:      types.SideSell,
   190  	}
   191  	engine.RegisterOrder(context.TODO(), passive)
   192  	engine.RegisterOrder(context.TODO(), aggressive)
   193  
   194  	trade := types.Trade{
   195  		Type:      types.TradeTypeDefault,
   196  		ID:        "trade_id",
   197  		MarketID:  "market_id",
   198  		Price:     price,
   199  		Size:      size,
   200  		Buyer:     buyer,
   201  		Seller:    seller,
   202  		BuyOrder:  "buy_order_id",
   203  		SellOrder: "sell_order_id",
   204  		Timestamp: time.Now().Unix(),
   205  	}
   206  	positions := engine.Update(context.Background(), &trade, passive, aggressive)
   207  	pos := engine.Positions()
   208  	assert.Equal(t, 2, len(pos))
   209  	assert.Equal(t, 2, len(positions))
   210  	for _, p := range pos {
   211  		if p.Party() == buyer {
   212  			assert.Equal(t, int64(size), p.Size())
   213  			assert.Equal(t, num.UintZero(), p.VWBuy())
   214  		} else {
   215  			assert.Equal(t, int64(-size), p.Size())
   216  			assert.Equal(t, num.UintZero(), p.VWSell())
   217  		}
   218  	}
   219  }
   220  
   221  func TestRemoveDistressedEmpty(t *testing.T) {
   222  	data := []events.MarketPosition{
   223  		mp{
   224  			party: "test",
   225  			size:  1,
   226  			price: num.NewUint(1000),
   227  		},
   228  	}
   229  	e := getTestEngine(t)
   230  	ret := e.RemoveDistressed(data)
   231  	assert.Empty(t, ret)
   232  }
   233  
   234  func TestRegisterUnregisterOrder(t *testing.T) {
   235  	t.Run("Test successful order register", testRegisterOrderSuccessful)
   236  	t.Run("Test successful order unregister", testUnregisterOrderSuccessful)
   237  	t.Run("Test unsuccessful order unregister", testUnregisterOrderUnsuccessful)
   238  }
   239  
   240  func testRegisterOrderSuccessful(t *testing.T) {
   241  	const (
   242  		buysize  int64 = 123
   243  		sellsize int64 = 456
   244  	)
   245  	e := getTestEngine(t)
   246  	orderBuy := types.Order{
   247  		Party:     "test_party",
   248  		Side:      types.SideBuy,
   249  		Size:      uint64(buysize),
   250  		Remaining: uint64(buysize),
   251  		Price:     num.UintZero(),
   252  	}
   253  	pos := e.RegisterOrder(context.TODO(), &orderBuy)
   254  	assert.Equal(t, buysize, pos.Buy())
   255  	assert.Zero(t, pos.Sell())
   256  	assert.True(t, pos.Price().IsZero())
   257  	assert.Zero(t, pos.Size())
   258  	positions := e.Positions()
   259  	assert.Equal(t, 1, len(positions))
   260  	assert.Equal(t, pos.Buy(), positions[0].Buy())
   261  
   262  	orderSell := types.Order{
   263  		Party:     "test_party",
   264  		Side:      types.SideSell,
   265  		Size:      uint64(sellsize),
   266  		Remaining: uint64(sellsize),
   267  		Price:     num.UintZero(),
   268  	}
   269  	pos = e.RegisterOrder(context.TODO(), &orderSell)
   270  	assert.Equal(t, buysize, pos.Buy())
   271  	assert.Equal(t, sellsize, pos.Sell())
   272  	assert.True(t, pos.Price().IsZero())
   273  	assert.Zero(t, pos.Size())
   274  	positions = e.Positions()
   275  	assert.Equal(t, 1, len(positions))
   276  	assert.Equal(t, pos.Buy(), positions[0].Buy())
   277  	assert.Equal(t, pos.Sell(), positions[0].Sell())
   278  }
   279  
   280  func testUnregisterOrderSuccessful(t *testing.T) {
   281  	const (
   282  		buysize  int64 = 123
   283  		sellsize int64 = 456
   284  	)
   285  	e := getTestEngine(t)
   286  	orderBuy := types.Order{
   287  		Party:     "test_party",
   288  		Side:      types.SideBuy,
   289  		Size:      uint64(buysize),
   290  		Remaining: uint64(buysize),
   291  		Price:     num.UintZero(),
   292  	}
   293  	pos := e.RegisterOrder(context.TODO(), &orderBuy)
   294  	assert.Equal(t, buysize, pos.Buy())
   295  
   296  	pos = e.UnregisterOrder(context.TODO(), &orderBuy)
   297  	assert.Zero(t, pos.Buy())
   298  
   299  	orderSell := types.Order{
   300  		Party:     "test_party",
   301  		Side:      types.SideSell,
   302  		Size:      uint64(sellsize),
   303  		Remaining: uint64(sellsize),
   304  		Price:     num.UintZero(),
   305  	}
   306  	pos = e.RegisterOrder(context.TODO(), &orderSell)
   307  	assert.Zero(t, pos.Buy())
   308  	assert.Equal(t, sellsize, pos.Sell())
   309  
   310  	pos = e.UnregisterOrder(context.TODO(), &orderSell)
   311  	assert.Zero(t, pos.Buy())
   312  	assert.Zero(t, pos.Sell())
   313  }
   314  
   315  func testUnregisterOrderUnsuccessful(t *testing.T) {
   316  	e := getTestEngine(t)
   317  	orderBuy := types.Order{
   318  		Party:     "test_party",
   319  		Side:      types.SideBuy,
   320  		Size:      uint64(999),
   321  		Remaining: uint64(999),
   322  		Price:     num.UintZero(),
   323  	}
   324  	require.Panics(t, func() {
   325  		_ = e.UnregisterOrder(context.TODO(), &orderBuy)
   326  	})
   327  }
   328  
   329  func getTestEngine(t *testing.T) *positions.SnapshotEngine {
   330  	t.Helper()
   331  	broker := stubs.NewBrokerStub()
   332  
   333  	return positions.NewSnapshotEngine(
   334  		logging.NewTestLogger(), positions.NewDefaultConfig(),
   335  		"test_market",
   336  		broker,
   337  	)
   338  }
   339  
   340  func TestGetOpenInterestGivenTrades(t *testing.T) {
   341  	// A, B represents partys who already have positions
   342  	// C, D represents partys who don't have positions (but there are entries in "trades" array that contain their trades)
   343  
   344  	cases := []struct {
   345  		ExistingPositions []*types.Trade
   346  		Trades            []*types.Trade
   347  		ExpectedOI        uint64
   348  	}{
   349  		// Both parties already have positions
   350  		{ // A: + 100, B: -100 => OI: 100
   351  			ExistingPositions: []*types.Trade{
   352  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   353  			},
   354  			ExpectedOI: 100,
   355  		},
   356  		{ // A: + 100 - 10, B: -100 + 10=> OI: 90
   357  			ExistingPositions: []*types.Trade{
   358  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   359  			},
   360  			Trades: []*types.Trade{
   361  				{Seller: "A", Buyer: "B", Size: 10, Price: num.UintZero()},
   362  			},
   363  			ExpectedOI: 90,
   364  		},
   365  		{
   366  			// A: + 100 + 10, B: -100 - 10 => OI: 110
   367  			ExistingPositions: []*types.Trade{
   368  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   369  			},
   370  			Trades: []*types.Trade{
   371  				{Seller: "B", Buyer: "A", Size: 10, Price: num.UintZero()},
   372  			},
   373  			ExpectedOI: 110,
   374  		},
   375  		{
   376  			// Same as above + wash trade -> should leave OI unchanged
   377  			ExistingPositions: []*types.Trade{
   378  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   379  			},
   380  			Trades: []*types.Trade{
   381  				{Seller: "B", Buyer: "A", Size: 10, Price: num.UintZero()},
   382  				{Seller: "A", Buyer: "A", Size: 13, Price: num.UintZero()},
   383  			},
   384  			ExpectedOI: 110,
   385  		},
   386  		{
   387  			// Same as above + wash trade -> should leave OI unchanged
   388  			ExistingPositions: []*types.Trade{},
   389  			Trades: []*types.Trade{
   390  				{Seller: "A", Buyer: "B", Size: 20, Price: num.UintZero()},
   391  				{Seller: "A", Buyer: "C", Size: 30, Price: num.UintZero()},
   392  				{Seller: "D", Buyer: "D", Size: 40, Price: num.UintZero()},
   393  			},
   394  			ExpectedOI: 50,
   395  		},
   396  		// There at least 1 new party
   397  		{
   398  			// A: + 100 + 10, B: -100, C: -10 => OI: 110
   399  			ExistingPositions: []*types.Trade{
   400  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   401  			},
   402  			Trades: []*types.Trade{
   403  				{Seller: "C", Buyer: "A", Size: 10, Price: num.UintZero()},
   404  			},
   405  			ExpectedOI: 110,
   406  		},
   407  		{
   408  			// A: + 100 - 10, B: -100, C: +10 => OI: 100
   409  			ExistingPositions: []*types.Trade{
   410  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   411  			},
   412  			Trades: []*types.Trade{
   413  				{Seller: "A", Buyer: "C", Size: 10, Price: num.UintZero()},
   414  			},
   415  			ExpectedOI: 100,
   416  		},
   417  		// None of the parties have positions yet
   418  		{
   419  			// C: +10, D:-10 => OI: 10
   420  			Trades: []*types.Trade{
   421  				{Seller: "D", Buyer: "C", Size: 10, Price: num.UintZero()},
   422  			},
   423  			ExpectedOI: 10,
   424  		},
   425  		{
   426  			ExistingPositions: []*types.Trade{
   427  				{Seller: "C", Buyer: "A", Size: 100, Price: num.UintZero()},
   428  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   429  			},
   430  			Trades: []*types.Trade{
   431  				{Seller: "B", Buyer: "A", Size: 5, Price: num.UintZero()},
   432  			},
   433  			ExpectedOI: 200,
   434  		},
   435  		{
   436  			ExistingPositions: []*types.Trade{
   437  				{Seller: "C", Buyer: "A", Size: 100, Price: num.UintZero()},
   438  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   439  			},
   440  			Trades: []*types.Trade{
   441  				{Seller: "A", Buyer: "B", Size: 5, Price: num.UintZero()},
   442  			},
   443  			ExpectedOI: 200,
   444  		},
   445  		{
   446  			ExistingPositions: []*types.Trade{
   447  				{Seller: "C", Buyer: "A", Size: 100, Price: num.UintZero()},
   448  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   449  			},
   450  			Trades: []*types.Trade{
   451  				{Seller: "C", Buyer: "B", Size: 5, Price: num.UintZero()},
   452  			},
   453  			ExpectedOI: 205,
   454  		},
   455  		{
   456  			ExistingPositions: []*types.Trade{
   457  				{Seller: "C", Buyer: "A", Size: 100, Price: num.UintZero()},
   458  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   459  			},
   460  			Trades: []*types.Trade{
   461  				{Seller: "B", Buyer: "C", Size: 5, Price: num.UintZero()},
   462  			},
   463  			ExpectedOI: 195,
   464  		},
   465  		{
   466  			ExistingPositions: []*types.Trade{
   467  				{Seller: "C", Buyer: "A", Size: 100, Price: num.UintZero()},
   468  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   469  			},
   470  			Trades: []*types.Trade{
   471  				{Seller: "D", Buyer: "C", Size: 500, Price: num.UintZero()},
   472  			},
   473  			ExpectedOI: 500,
   474  		},
   475  		{
   476  			ExistingPositions: []*types.Trade{
   477  				{Seller: "C", Buyer: "A", Size: 100, Price: num.UintZero()},
   478  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   479  			},
   480  			Trades: []*types.Trade{
   481  				{Seller: "D", Buyer: "C", Size: 5, Price: num.UintZero()},
   482  			},
   483  			ExpectedOI: 200,
   484  		},
   485  		{
   486  			ExistingPositions: []*types.Trade{
   487  				{Seller: "C", Buyer: "A", Size: 10, Price: num.UintZero()},
   488  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   489  			},
   490  			Trades: []*types.Trade{
   491  				{Seller: "A", Buyer: "B", Size: 5, Price: num.UintZero()},
   492  			},
   493  			ExpectedOI: 110,
   494  		},
   495  		{
   496  			ExistingPositions: []*types.Trade{
   497  				{Seller: "C", Buyer: "A", Size: 10, Price: num.UintZero()},
   498  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   499  			},
   500  			Trades: []*types.Trade{
   501  				{Seller: "B", Buyer: "A", Size: 5, Price: num.UintZero()},
   502  			},
   503  			ExpectedOI: 110,
   504  		},
   505  		{
   506  			ExistingPositions: []*types.Trade{
   507  				{Seller: "C", Buyer: "A", Size: 10, Price: num.UintZero()},
   508  				{Seller: "C", Buyer: "B", Size: 100, Price: num.UintZero()},
   509  			},
   510  			Trades: []*types.Trade{
   511  				{Seller: "A", Buyer: "B", Size: 200, Price: num.UintZero()},
   512  			},
   513  			ExpectedOI: 300,
   514  		},
   515  		{
   516  			ExistingPositions: []*types.Trade{
   517  				{Seller: "A", Buyer: "C", Size: 100, Price: num.UintZero()},
   518  				{Seller: "C", Buyer: "B", Size: 300, Price: num.UintZero()},
   519  			},
   520  			Trades: []*types.Trade{
   521  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   522  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   523  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   524  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   525  			},
   526  			ExpectedOI: 300,
   527  		},
   528  		{
   529  			ExistingPositions: []*types.Trade{
   530  				{Seller: "A", Buyer: "C", Size: 100, Price: num.UintZero()},
   531  				{Seller: "C", Buyer: "B", Size: 300, Price: num.UintZero()},
   532  			},
   533  			Trades: []*types.Trade{
   534  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   535  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   536  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   537  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   538  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   539  			},
   540  			ExpectedOI: 400,
   541  		},
   542  		{
   543  			ExistingPositions: []*types.Trade{
   544  				{Seller: "A", Buyer: "C", Size: 100, Price: num.UintZero()},
   545  				{Seller: "C", Buyer: "B", Size: 300, Price: num.UintZero()},
   546  			},
   547  			Trades: []*types.Trade{
   548  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   549  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   550  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   551  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   552  				{Seller: "A", Buyer: "B", Size: 100, Price: num.UintZero()},
   553  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   554  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   555  			},
   556  			ExpectedOI: 400,
   557  		},
   558  		{
   559  			ExistingPositions: []*types.Trade{
   560  				{Seller: "A", Buyer: "C", Size: 100, Price: num.UintZero()},
   561  				{Seller: "C", Buyer: "B", Size: 300, Price: num.UintZero()},
   562  			},
   563  			Trades: []*types.Trade{
   564  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   565  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   566  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   567  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   568  				{Seller: "A", Buyer: "C", Size: 100, Price: num.UintZero()},
   569  				{Seller: "D", Buyer: "A", Size: 100, Price: num.UintZero()},
   570  				{Seller: "B", Buyer: "A", Size: 100, Price: num.UintZero()},
   571  			},
   572  			ExpectedOI: 400,
   573  		},
   574  	}
   575  
   576  	for _, tc := range cases {
   577  		e := getTestEngine(t)
   578  
   579  		for _, tr := range tc.ExistingPositions {
   580  			passive := registerOrder(e, types.SideBuy, tr.Buyer, tr.Price, tr.Size)
   581  			aggressive := registerOrder(e, types.SideSell, tr.Seller, tr.Price, tr.Size)
   582  			e.Update(context.Background(), tr, passive, aggressive)
   583  		}
   584  
   585  		oiGivenTrades := e.GetOpenInterestGivenTrades(tc.Trades)
   586  
   587  		for _, tr := range tc.Trades {
   588  			passive := registerOrder(e, types.SideBuy, tr.Buyer, tr.Price, tr.Size)
   589  			aggressive := registerOrder(e, types.SideSell, tr.Seller, tr.Price, tr.Size)
   590  			e.Update(context.Background(), tr, passive, aggressive)
   591  		}
   592  
   593  		// Now check it matches once those trades are registered as positions
   594  		oiAfterUpdatingPositions := e.GetOpenInterest()
   595  		t.Run("", func(t *testing.T) {
   596  			require.Equal(t, tc.ExpectedOI, oiGivenTrades)
   597  			require.Equal(t, tc.ExpectedOI, oiAfterUpdatingPositions)
   598  		})
   599  	}
   600  }
   601  
   602  type mp struct {
   603  	size, buy, sell int64
   604  	party           string
   605  	price           *num.Uint
   606  }
   607  
   608  func (m mp) AverageEntryPrice() *num.Uint {
   609  	return num.UintZero()
   610  }
   611  
   612  func (m mp) Party() string {
   613  	return m.party
   614  }
   615  
   616  func (m mp) Size() int64 {
   617  	return m.size
   618  }
   619  
   620  func (m mp) Buy() int64 {
   621  	return m.buy
   622  }
   623  
   624  func (m mp) Sell() int64 {
   625  	return m.sell
   626  }
   627  
   628  func (m mp) Price() *num.Uint {
   629  	return m.price
   630  }
   631  
   632  func (m mp) ClearPotentials() {}
   633  
   634  func (m mp) BuySumProduct() *num.Uint {
   635  	return num.UintZero()
   636  }
   637  
   638  func (m mp) SellSumProduct() *num.Uint {
   639  	return num.UintZero()
   640  }
   641  
   642  func (m mp) VWBuy() *num.Uint {
   643  	return num.UintZero()
   644  }
   645  
   646  func (m mp) VWSell() *num.Uint {
   647  	return num.UintZero()
   648  }
   649  
   650  func TestHash(t *testing.T) {
   651  	e := getTestEngine(t)
   652  	orders := []*types.Order{
   653  		{
   654  			Party:     "test_party_1",
   655  			Side:      types.SideBuy,
   656  			Size:      uint64(100),
   657  			Remaining: uint64(100),
   658  			Price:     num.UintZero(),
   659  		},
   660  		{
   661  			Party:     "test_party_2",
   662  			Side:      types.SideBuy,
   663  			Size:      uint64(200),
   664  			Remaining: uint64(200),
   665  			Price:     num.UintZero(),
   666  		},
   667  	}
   668  
   669  	matchingPrice := num.NewUint(10000)
   670  	tradeSize := uint64(15)
   671  	passiveOrder := &types.Order{
   672  		ID:        "buy_order_id",
   673  		Party:     "test_party_3",
   674  		Side:      types.SideBuy,
   675  		Size:      tradeSize,
   676  		Remaining: tradeSize,
   677  		Price:     matchingPrice,
   678  	}
   679  
   680  	aggresiveOrder := &types.Order{
   681  		ID:        "sell_order_id",
   682  		Party:     "test_party_1",
   683  		Side:      types.SideSell,
   684  		Size:      tradeSize,
   685  		Remaining: tradeSize,
   686  		Price:     matchingPrice,
   687  	}
   688  
   689  	orders = append(orders, passiveOrder, aggresiveOrder)
   690  
   691  	for _, order := range orders {
   692  		e.RegisterOrder(context.TODO(), order)
   693  	}
   694  
   695  	trade := types.Trade{
   696  		Type:      types.TradeTypeDefault,
   697  		ID:        "trade_id",
   698  		MarketID:  "market_id",
   699  		Price:     matchingPrice,
   700  		Size:      tradeSize,
   701  		Buyer:     passiveOrder.Party,
   702  		Seller:    aggresiveOrder.Party,
   703  		BuyOrder:  passiveOrder.ID,
   704  		SellOrder: aggresiveOrder.ID,
   705  		Timestamp: time.Now().Unix(),
   706  	}
   707  	e.Update(context.Background(), &trade, passiveOrder, aggresiveOrder)
   708  
   709  	hash := e.Hash()
   710  	require.Equal(t,
   711  		"05f6edb5f12dff7edd911da41da5962631283a01e13a717d193109454d22d10a",
   712  		hex.EncodeToString(hash),
   713  		"It should match against the known hash",
   714  	)
   715  
   716  	// compute the hash 100 times for determinism verification
   717  	for i := 0; i < 100; i++ {
   718  		got := e.Hash()
   719  		require.Equal(t, hash, got)
   720  	}
   721  }
   722  
   723  func registerOrder(e *positions.SnapshotEngine, side types.Side, party string, price *num.Uint, size uint64) *types.Order {
   724  	order := &types.Order{
   725  		Party:     party,
   726  		Side:      side,
   727  		Price:     price,
   728  		Remaining: size,
   729  	}
   730  	e.RegisterOrder(context.TODO(), order)
   731  	return order
   732  }
   733  
   734  func TestCalcVWAP(t *testing.T) {
   735  	// no previous size, new long position acquired at 100
   736  	require.Equal(t, num.NewUint(100), positions.CalcVWAP(num.UintZero(), 0, 10, num.NewUint(100)))
   737  
   738  	// position decreased, not flipping sides
   739  	require.Equal(t, num.NewUint(100), positions.CalcVWAP(num.NewUint(100), 10, -5, num.NewUint(25)))
   740  
   741  	// position closed
   742  	require.Equal(t, num.UintZero(), positions.CalcVWAP(num.NewUint(100), 10, -10, num.NewUint(25)))
   743  
   744  	// no previous size, new short position acquired at 100
   745  	require.Equal(t, num.NewUint(100), positions.CalcVWAP(num.UintZero(), 0, -10, num.NewUint(100)))
   746  
   747  	// position decreased, not flipping sides
   748  	require.Equal(t, num.NewUint(100), positions.CalcVWAP(num.NewUint(100), -10, 5, num.NewUint(25)))
   749  
   750  	// position closed
   751  	require.Equal(t, num.UintZero(), positions.CalcVWAP(num.NewUint(100), -10, 10, num.NewUint(25)))
   752  
   753  	// long position increased => (100 * 10 + 25 * 5) / 15
   754  	require.Equal(t, num.NewUint(75), positions.CalcVWAP(num.NewUint(100), 10, 5, num.NewUint(25)))
   755  
   756  	// short position increased => (100 * -10 + 25 * -5) / 15
   757  	require.Equal(t, num.NewUint(75), positions.CalcVWAP(num.NewUint(100), -10, -5, num.NewUint(25)))
   758  
   759  	// flipping from long to short => (100 * 10 + 15 * 15)/25
   760  	require.Equal(t, num.NewUint(49), positions.CalcVWAP(num.NewUint(100), -10, 25, num.NewUint(15)))
   761  
   762  	// flipping from short to long => (100 * 10 + 15 * 15)/25
   763  	require.Equal(t, num.NewUint(49), positions.CalcVWAP(num.NewUint(100), 10, -25, num.NewUint(15)))
   764  }