code.vegaprotocol.io/vega@v0.79.0/core/positions/positions_acceptance_criteria_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  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  )
    29  
    30  func TestPositionsEngineAcceptanceCriteria(t *testing.T) {
    31  	t.Run("Open long position, trades occur increasing long position", testTradeOccurIncreaseShortAndLong)
    32  	t.Run("Open long position, trades occur decreasing long position", testTradeOccurDecreaseShortAndLong)
    33  	t.Run("Open short position, trades occur increasing (greater abs(size)) short position", testTradeOccurIncreaseShortAndLong)
    34  	t.Run("Open short position, trades occur decreasing (smaller abs(size)) short position", testTradeOccurDecreaseShortAndLong)
    35  	t.Run("Open short position, trades occur taking position to zero (closing it)", testTradeOccurClosingShortAndLong)
    36  	t.Run("Open long position, trades occur taking position to zero (closing it)", testTradeOccurClosingShortAndLong)
    37  	t.Run("Open short position, trades occur closing the short position and opening a long position", testTradeOccurShortBecomeLongAndLongBecomeShort)
    38  	t.Run("Open long position, trades occur closing the long position and opening a short position", testTradeOccurShortBecomeLongAndLongBecomeShort)
    39  	t.Run("No open position, trades occur opening a long position", testNoOpenPositionsTradeOccurOpenLongAndShortPosition)
    40  	t.Run("No open position, trades occur opening a short position", testNoOpenPositionsTradeOccurOpenLongAndShortPosition)
    41  	t.Run("Open position, trades occur that close it (take it to zero), in a separate transaction, trades occur and that open a new position", testOpenPosTradeOccurCloseThanOpenPositioAgain)
    42  	// NOTE: this will not be tested, as we do not remove a position from the engine when it reach 0
    43  	// Opening and closing positions for multiple partys, maintains position size for all open (non-zero) positions
    44  	t.Run("Does not change position size for a wash trade (buyer = seller)", testWashTradeDoNotChangePosition)
    45  
    46  	// No active buy orders, a new buy order is added to the order book
    47  	t.Run("Active buy orders, a new buy order is added to the order book", testNewOrderAddedToTheBook)
    48  	t.Run("Active sell orders, a new sell order is added to the order book", testNewOrderAddedToTheBook)
    49  	t.Run("Active buy order, an order initiated by another party causes a partial amount of the existing buy order to trade.", testNewTradePartialAmountOfExistingOrderTraded)
    50  	t.Run("Active sell order, an order initiated by another party causes a partial amount of the existing sell order to trade.", testNewTradePartialAmountOfExistingOrderTraded)
    51  	t.Run("Active buy order, an order initiated by another party causes the full amount of the existing buy order to trade.", testTradeCauseTheFullAmountOfOrderToTrade)
    52  	t.Run("Active sell order, an order initiated by another party causes the full amount of the existing sell order to trade.", testTradeCauseTheFullAmountOfOrderToTrade)
    53  	t.Run("Active buy orders, an existing order is cancelled", testOrderCancelled)
    54  	t.Run("Active sell orders, an existing order is cancelled", testOrderCancelled)
    55  	t.Run("Aggressive order gets partially filled", testNewTradePartialAmountOfIncomingOrderTraded)
    56  
    57  	// NOTE: these next tests needs the integration test to be ran
    58  	// Active buy orders, an existing buy order is amended which increases its size.
    59  	// Active buy orders, an existing buy order is amended which decreases its size.
    60  	// Active buy orders, an existing buy order's price is amended such that it trades a partial amount.
    61  	// Active buy orders, an existing buy order's price is amended such that it trades in full.
    62  	// Active buy orders, an existing order expires
    63  }
    64  
    65  func testTradeOccurIncreaseShortAndLong(t *testing.T) {
    66  	engine := getTestEngine(t)
    67  	assert.Empty(t, engine.Positions())
    68  	buyer := "buyer_id"
    69  	seller := "seller_id"
    70  	cases := []struct {
    71  		trade              types.Trade
    72  		expectedSizePartyA int64
    73  		expectedSizePartyB int64
    74  	}{
    75  		{
    76  			trade: types.Trade{
    77  				Type:      types.TradeTypeDefault,
    78  				ID:        "trade_id",
    79  				MarketID:  "market_id",
    80  				Price:     num.NewUint(100),
    81  				Size:      10,
    82  				Buyer:     buyer,
    83  				Seller:    seller,
    84  				BuyOrder:  "buy_order_id",
    85  				SellOrder: "sell_order_id",
    86  				Timestamp: time.Now().Unix(),
    87  			},
    88  			expectedSizePartyA: +10,
    89  			expectedSizePartyB: -10,
    90  		},
    91  		{
    92  			trade: types.Trade{
    93  				Type:      types.TradeTypeDefault,
    94  				ID:        "trade_id",
    95  				MarketID:  "market_id",
    96  				Price:     num.NewUint(100),
    97  				Size:      25,
    98  				Buyer:     buyer,
    99  				Seller:    seller,
   100  				BuyOrder:  "buy_order_id",
   101  				SellOrder: "sell_order_id",
   102  				Timestamp: time.Now().Unix(),
   103  			},
   104  			expectedSizePartyA: +35,
   105  			expectedSizePartyB: -35,
   106  		},
   107  	}
   108  
   109  	for _, c := range cases {
   110  		// call an update on the positions with the trade
   111  		passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size)
   112  		aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size)
   113  		positions := engine.Update(context.Background(), &c.trade, passive, aggressive)
   114  		pos := engine.Positions()
   115  		assert.Equal(t, 2, len(pos))
   116  		assert.Equal(t, 2, len(positions))
   117  
   118  		// check size of positions
   119  		for _, p := range pos {
   120  			if p.Party() == buyer {
   121  				assert.Equal(t, c.expectedSizePartyA, p.Size())
   122  			} else if p.Party() == seller {
   123  				assert.Equal(t, c.expectedSizePartyB, p.Size())
   124  			}
   125  		}
   126  	}
   127  }
   128  
   129  func testTradeOccurDecreaseShortAndLong(t *testing.T) {
   130  	engine := getTestEngine(t)
   131  	assert.Empty(t, engine.Positions())
   132  	partyA := "party_a"
   133  	partyB := "party_b"
   134  	cases := []struct {
   135  		trade              types.Trade
   136  		expectedSizePartyA int64
   137  		expectedSizePartyB int64
   138  	}{
   139  		{
   140  			trade: types.Trade{
   141  				Type:      types.TradeTypeDefault,
   142  				ID:        "trade_i1",
   143  				MarketID:  "market_id",
   144  				Price:     num.NewUint(100),
   145  				Size:      10,
   146  				Buyer:     partyA,
   147  				Seller:    partyB,
   148  				BuyOrder:  "buy_order_id1",
   149  				SellOrder: "sell_order_id1",
   150  				Timestamp: time.Now().Unix(),
   151  			},
   152  			expectedSizePartyA: +10,
   153  			expectedSizePartyB: -10,
   154  		},
   155  		// inverse buyer and seller, so it should reduce both position of 5
   156  		{
   157  			trade: types.Trade{
   158  				Type:      types.TradeTypeDefault,
   159  				ID:        "trade_id2",
   160  				MarketID:  "market_id",
   161  				Price:     num.NewUint(100),
   162  				Size:      5,
   163  				Buyer:     partyB,
   164  				Seller:    partyA,
   165  				BuyOrder:  "buy_order_id2",
   166  				SellOrder: "sell_order_id2",
   167  				Timestamp: time.Now().Unix(),
   168  			},
   169  			expectedSizePartyA: +5,
   170  			expectedSizePartyB: -5,
   171  		},
   172  	}
   173  
   174  	for _, c := range cases {
   175  		// call an update on the positions with the trade
   176  		passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size)
   177  		aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size)
   178  		positions := engine.Update(context.Background(), &c.trade, passive, aggressive)
   179  		pos := engine.Positions()
   180  		assert.Equal(t, 2, len(pos))
   181  		assert.Equal(t, 2, len(positions))
   182  
   183  		// check size of positions
   184  		for _, p := range pos {
   185  			if p.Party() == partyA {
   186  				assert.Equal(t, c.expectedSizePartyA, p.Size())
   187  			} else if p.Party() == partyB {
   188  				assert.Equal(t, c.expectedSizePartyB, p.Size())
   189  			}
   190  		}
   191  	}
   192  }
   193  
   194  func testTradeOccurClosingShortAndLong(t *testing.T) {
   195  	engine := getTestEngine(t)
   196  	assert.Empty(t, engine.Positions())
   197  	partyA := "party_a"
   198  	partyB := "party_b"
   199  	cases := []struct {
   200  		trade              types.Trade
   201  		expectedSizePartyA int64
   202  		expectedSizePartyB int64
   203  	}{
   204  		{
   205  			trade: types.Trade{
   206  				Type:      types.TradeTypeDefault,
   207  				ID:        "trade_i1",
   208  				MarketID:  "market_id",
   209  				Price:     num.NewUint(100),
   210  				Size:      10,
   211  				Buyer:     partyA,
   212  				Seller:    partyB,
   213  				BuyOrder:  "buy_order_id1",
   214  				SellOrder: "sell_order_id1",
   215  				Timestamp: time.Now().Unix(),
   216  			},
   217  			expectedSizePartyA: +10,
   218  			expectedSizePartyB: -10,
   219  		},
   220  		// inverse buyer and seller, so it should reduce both position of 5
   221  		{
   222  			trade: types.Trade{
   223  				Type:      types.TradeTypeDefault,
   224  				ID:        "trade_id2",
   225  				MarketID:  "market_id",
   226  				Price:     num.NewUint(100),
   227  				Size:      10,
   228  				Buyer:     partyB,
   229  				Seller:    partyA,
   230  				BuyOrder:  "buy_order_id2",
   231  				SellOrder: "sell_order_id2",
   232  				Timestamp: time.Now().Unix(),
   233  			},
   234  			expectedSizePartyA: 0,
   235  			expectedSizePartyB: 0,
   236  		},
   237  	}
   238  
   239  	for _, c := range cases {
   240  		passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size)
   241  		aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size)
   242  		positions := engine.Update(context.Background(), &c.trade, passive, aggressive)
   243  		pos := engine.Positions()
   244  		assert.Equal(t, 2, len(pos))
   245  		assert.Equal(t, 2, len(positions))
   246  
   247  		// check size of positions
   248  		for _, p := range pos {
   249  			if p.Party() == partyA {
   250  				assert.Equal(t, c.expectedSizePartyA, p.Size())
   251  			} else if p.Party() == partyB {
   252  				assert.Equal(t, c.expectedSizePartyB, p.Size())
   253  			}
   254  		}
   255  	}
   256  }
   257  
   258  func testTradeOccurShortBecomeLongAndLongBecomeShort(t *testing.T) {
   259  	engine := getTestEngine(t)
   260  	assert.Empty(t, engine.Positions())
   261  	partyA := "party_a"
   262  	partyB := "party_b"
   263  	cases := []struct {
   264  		trade              types.Trade
   265  		expectedSizePartyA int64
   266  		expectedSizePartyB int64
   267  	}{
   268  		{
   269  			trade: types.Trade{
   270  				Type:      types.TradeTypeDefault,
   271  				ID:        "trade_i1",
   272  				MarketID:  "market_id",
   273  				Price:     num.NewUint(100),
   274  				Size:      10,
   275  				Buyer:     partyA,
   276  				Seller:    partyB,
   277  				BuyOrder:  "buy_order_id1",
   278  				SellOrder: "sell_order_id1",
   279  				Timestamp: time.Now().Unix(),
   280  			},
   281  			expectedSizePartyA: +10,
   282  			expectedSizePartyB: -10,
   283  		},
   284  		// inverse buyer and seller, so it should reduce both position of 5
   285  		{
   286  			trade: types.Trade{
   287  				Type:      types.TradeTypeDefault,
   288  				ID:        "trade_id2",
   289  				MarketID:  "market_id",
   290  				Price:     num.NewUint(100),
   291  				Size:      15,
   292  				Buyer:     partyB,
   293  				Seller:    partyA,
   294  				BuyOrder:  "buy_order_id2",
   295  				SellOrder: "sell_order_id2",
   296  				Timestamp: time.Now().Unix(),
   297  			},
   298  			expectedSizePartyA: -5,
   299  			expectedSizePartyB: +5,
   300  		},
   301  	}
   302  
   303  	for _, c := range cases {
   304  		passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size)
   305  		aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size)
   306  		// call an update on the positions with the trade
   307  		positions := engine.Update(context.Background(), &c.trade, passive, aggressive)
   308  		pos := engine.Positions()
   309  		assert.Equal(t, 2, len(pos))
   310  		assert.Equal(t, 2, len(positions))
   311  
   312  		// check size of positions
   313  		for _, p := range pos {
   314  			if p.Party() == partyA {
   315  				assert.Equal(t, c.expectedSizePartyA, p.Size())
   316  			} else if p.Party() == partyB {
   317  				assert.Equal(t, c.expectedSizePartyB, p.Size())
   318  			}
   319  		}
   320  	}
   321  }
   322  
   323  func testNoOpenPositionsTradeOccurOpenLongAndShortPosition(t *testing.T) {
   324  	engine := getTestEngine(t)
   325  	partyA := "party_a"
   326  	partyB := "party_b"
   327  	c := struct {
   328  		trade              types.Trade
   329  		expectedSizePartyA int64
   330  		expectedSizePartyB int64
   331  	}{
   332  		trade: types.Trade{
   333  			Type:      types.TradeTypeDefault,
   334  			ID:        "trade_i1",
   335  			MarketID:  "market_id",
   336  			Price:     num.NewUint(100),
   337  			Size:      10,
   338  			Buyer:     partyA,
   339  			Seller:    partyB,
   340  			BuyOrder:  "buy_order_id1",
   341  			SellOrder: "sell_order_id1",
   342  			Timestamp: time.Now().Unix(),
   343  		},
   344  		expectedSizePartyA: +10,
   345  		expectedSizePartyB: -10,
   346  	}
   347  
   348  	// ensure there is no open positions in the engine
   349  	assert.Empty(t, engine.Positions())
   350  
   351  	// now create a trade an make sure the positions are created an correct
   352  	passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size)
   353  	aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size)
   354  	positions := engine.Update(context.Background(), &c.trade, passive, aggressive)
   355  	pos := engine.Positions()
   356  	assert.Equal(t, 2, len(pos))
   357  	assert.Equal(t, 2, len(positions))
   358  
   359  	// check size of positions
   360  	for _, p := range pos {
   361  		if p.Party() == partyA {
   362  			assert.Equal(t, c.expectedSizePartyA, p.Size())
   363  		} else if p.Party() == partyB {
   364  			assert.Equal(t, c.expectedSizePartyB, p.Size())
   365  		}
   366  	}
   367  }
   368  
   369  func testOpenPosTradeOccurCloseThanOpenPositioAgain(t *testing.T) {
   370  	engine := getTestEngine(t)
   371  	assert.Empty(t, engine.Positions())
   372  	partyA := "party_a"
   373  	partyB := "party_b"
   374  	partyC := "party_c"
   375  	cases := []struct {
   376  		trade              types.Trade
   377  		expectedSizePartyA int64
   378  		expectedSizePartyB int64
   379  		expectedSizePartyC int64
   380  		posSize            int
   381  	}{
   382  		// first trade between A and B, open a new position
   383  		{
   384  			trade: types.Trade{
   385  				Type:      types.TradeTypeDefault,
   386  				ID:        "trade_i1",
   387  				MarketID:  "market_id",
   388  				Price:     num.NewUint(100),
   389  				Size:      10,
   390  				Buyer:     partyA,
   391  				Seller:    partyB,
   392  				BuyOrder:  "buy_order_id1",
   393  				SellOrder: "sell_order_id1",
   394  				Timestamp: time.Now().Unix(),
   395  			},
   396  			expectedSizePartyA: +10,
   397  			expectedSizePartyB: -10,
   398  			expectedSizePartyC: 0,
   399  			posSize:            2,
   400  		},
   401  		// second trade between A and C, open C close A
   402  		{
   403  			trade: types.Trade{
   404  				Type:      types.TradeTypeDefault,
   405  				ID:        "trade_id2",
   406  				MarketID:  "market_id",
   407  				Price:     num.NewUint(100),
   408  				Size:      10,
   409  				Buyer:     partyC,
   410  				Seller:    partyA,
   411  				BuyOrder:  "buy_order_id2",
   412  				SellOrder: "sell_order_id2",
   413  				Timestamp: time.Now().Unix(),
   414  			},
   415  			expectedSizePartyA: 0,
   416  			expectedSizePartyB: -10,
   417  			expectedSizePartyC: 10,
   418  			posSize:            3,
   419  		},
   420  		// last trade between A and B again, re-open A, decrease B
   421  		{
   422  			trade: types.Trade{
   423  				Type:      types.TradeTypeDefault,
   424  				ID:        "trade_id3",
   425  				MarketID:  "market_id",
   426  				Price:     num.NewUint(100),
   427  				Size:      3,
   428  				Buyer:     partyB,
   429  				Seller:    partyA,
   430  				BuyOrder:  "buy_order_id3",
   431  				SellOrder: "sell_order_id3",
   432  				Timestamp: time.Now().Unix(),
   433  			},
   434  			expectedSizePartyA: -3,
   435  			expectedSizePartyB: -7,
   436  			expectedSizePartyC: 10,
   437  			posSize:            3,
   438  		},
   439  	}
   440  
   441  	for _, c := range cases {
   442  		passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size)
   443  		aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size)
   444  		positions := engine.Update(context.Background(), &c.trade, passive, aggressive)
   445  		pos := engine.Positions()
   446  		assert.Equal(t, c.posSize, len(pos), fmt.Sprintf("all pos trade: %v", c.trade.ID))
   447  		assert.Equal(t, 2, len(positions), fmt.Sprintf("chan trade: %v", c.trade.ID))
   448  
   449  		// check size of positions
   450  		for _, p := range pos {
   451  			if p.Party() == partyA {
   452  				assert.Equal(t, c.expectedSizePartyA, p.Size())
   453  			} else if p.Party() == partyB {
   454  				assert.Equal(t, c.expectedSizePartyB, p.Size())
   455  			} else if p.Party() == partyC {
   456  				assert.Equal(t, c.expectedSizePartyC, p.Size())
   457  			}
   458  		}
   459  	}
   460  }
   461  
   462  func testWashTradeDoNotChangePosition(t *testing.T) {
   463  	engine := getTestEngine(t)
   464  	assert.Empty(t, engine.Positions())
   465  	partyA := "party_a"
   466  	partyB := "party_b"
   467  	cases := []struct {
   468  		trade              types.Trade
   469  		expectedSizePartyA int64
   470  		expectedSizePartyB int64
   471  	}{
   472  		{
   473  			trade: types.Trade{
   474  				Type:      types.TradeTypeDefault,
   475  				ID:        "trade_i1",
   476  				MarketID:  "market_id",
   477  				Price:     num.NewUint(100),
   478  				Size:      10,
   479  				Buyer:     partyA,
   480  				Seller:    partyB,
   481  				BuyOrder:  "buy_order_id1",
   482  				SellOrder: "sell_order_id1",
   483  				Timestamp: time.Now().Unix(),
   484  			},
   485  			expectedSizePartyA: +10,
   486  			expectedSizePartyB: -10,
   487  		},
   488  		// party A trade with himsefl, no positions changes
   489  		{
   490  			trade: types.Trade{
   491  				Type:      types.TradeTypeDefault,
   492  				ID:        "trade_id2",
   493  				MarketID:  "market_id",
   494  				Price:     num.NewUint(100),
   495  				Size:      30,
   496  				Buyer:     partyA,
   497  				Seller:    partyA,
   498  				BuyOrder:  "buy_order_id2",
   499  				SellOrder: "sell_order_id2",
   500  				Timestamp: time.Now().Unix(),
   501  			},
   502  			expectedSizePartyA: +10,
   503  			expectedSizePartyB: -10,
   504  		},
   505  	}
   506  
   507  	for _, c := range cases {
   508  		passive := registerOrder(engine, types.SideBuy, c.trade.Buyer, c.trade.Price, c.trade.Size)
   509  		aggressive := registerOrder(engine, types.SideSell, c.trade.Seller, c.trade.Price, c.trade.Size)
   510  		// call an update on the positions with the trade
   511  		positions := engine.Update(context.Background(), &c.trade, passive, aggressive)
   512  		pos := engine.Positions()
   513  		assert.Equal(t, 2, len(pos))
   514  		assert.Equal(t, 2, len(positions))
   515  
   516  		// check size of positions
   517  		for _, p := range pos {
   518  			if p.Party() == partyA {
   519  				assert.Equal(t, c.expectedSizePartyA, p.Size())
   520  			} else if p.Party() == partyB {
   521  				assert.Equal(t, c.expectedSizePartyB, p.Size())
   522  			}
   523  		}
   524  	}
   525  }
   526  
   527  func testNewOrderAddedToTheBook(t *testing.T) {
   528  	engine := getTestEngine(t)
   529  	partyA := "party_a"
   530  	partyB := "party_b"
   531  	cases := []struct {
   532  		order        types.Order
   533  		expectedBuy  int64
   534  		expectedSell int64
   535  		expectedSize int64
   536  	}{
   537  		{
   538  			// add an original buy order for A
   539  			order: types.Order{
   540  				Size:      10,
   541  				Remaining: 10,
   542  				Party:     partyA,
   543  				Side:      types.SideBuy,
   544  				Price:     num.UintZero(),
   545  			},
   546  			expectedBuy:  10,
   547  			expectedSell: 0,
   548  			expectedSize: 0,
   549  		},
   550  		{
   551  			// add and original sell order for B
   552  			order: types.Order{
   553  				Size:      16,
   554  				Remaining: 16,
   555  				Party:     partyB,
   556  				Side:      types.SideSell,
   557  				Price:     num.UintZero(),
   558  			},
   559  			expectedBuy:  0,
   560  			expectedSell: 16,
   561  			expectedSize: 0,
   562  		},
   563  		{
   564  			// update buy order for A
   565  			order: types.Order{
   566  				Size:      17,
   567  				Remaining: 17,
   568  				Party:     partyA,
   569  				Side:      types.SideBuy,
   570  				Price:     num.UintZero(),
   571  			},
   572  			expectedBuy:  27,
   573  			expectedSell: 0,
   574  			expectedSize: 0,
   575  		},
   576  		{
   577  			// update sell order for B
   578  			order: types.Order{
   579  				Size:      5,
   580  				Remaining: 5,
   581  				Party:     partyB,
   582  				Side:      types.SideSell,
   583  				Price:     num.UintZero(),
   584  			},
   585  			expectedBuy:  0,
   586  			expectedSell: 21,
   587  			expectedSize: 0,
   588  		},
   589  	}
   590  
   591  	// no potions exists at the moment:
   592  	assert.Empty(t, engine.Positions())
   593  
   594  	for _, c := range cases {
   595  		pos := engine.RegisterOrder(context.TODO(), &c.order)
   596  		assert.Equal(t, c.expectedBuy, pos.Buy())
   597  		assert.Equal(t, c.expectedSell, pos.Sell())
   598  		assert.Equal(t, c.expectedSize, pos.Size())
   599  	}
   600  }
   601  
   602  func testNewTradePartialAmountOfExistingOrderTraded(t *testing.T) {
   603  	engine := getTestEngine(t)
   604  	partyA := "party_a"
   605  	partyB := "party_b"
   606  	matchingPrice := num.NewUint(100)
   607  	tradeSize := uint64(3)
   608  
   609  	passive := &types.Order{
   610  		Size:      7 + tradeSize,
   611  		Remaining: 7 + tradeSize,
   612  		Party:     partyA,
   613  		Side:      types.SideBuy,
   614  		Price:     matchingPrice,
   615  	}
   616  
   617  	aggressive := &types.Order{
   618  		Size:      tradeSize,
   619  		Remaining: tradeSize,
   620  		Party:     partyB,
   621  		Side:      types.SideSell,
   622  		Price:     matchingPrice,
   623  	}
   624  
   625  	cases := struct {
   626  		orders  []*types.Order
   627  		expects map[string]struct {
   628  			expectedBuy    int64
   629  			expectedSell   int64
   630  			expectedSize   int64
   631  			expectedVwBuy  *num.Uint
   632  			expectedVwSell *num.Uint
   633  		}
   634  	}{
   635  		orders: []*types.Order{
   636  			passive,
   637  			aggressive,
   638  			{
   639  				Size:      16 - tradeSize,
   640  				Remaining: 16 - tradeSize,
   641  				Party:     partyB,
   642  				Side:      types.SideSell,
   643  				Price:     num.NewUint(1000),
   644  			},
   645  		},
   646  		expects: map[string]struct {
   647  			expectedBuy    int64
   648  			expectedSell   int64
   649  			expectedSize   int64
   650  			expectedVwBuy  *num.Uint
   651  			expectedVwSell *num.Uint
   652  		}{
   653  			partyA: {
   654  				expectedBuy:    10,
   655  				expectedSell:   0,
   656  				expectedSize:   0,
   657  				expectedVwBuy:  passive.Price,
   658  				expectedVwSell: num.UintZero(),
   659  			},
   660  			partyB: {
   661  				expectedBuy:    0,
   662  				expectedSell:   16,
   663  				expectedSize:   0,
   664  				expectedVwBuy:  num.UintOne(),
   665  				expectedVwSell: num.NewUint(831), // 831.25
   666  			},
   667  		},
   668  	}
   669  
   670  	// no positions exists at the moment:
   671  	assert.Empty(t, engine.Positions())
   672  
   673  	for _, c := range cases.orders {
   674  		engine.RegisterOrder(context.TODO(), c)
   675  	}
   676  	pos := engine.Positions()
   677  	assert.Len(t, pos, len(cases.expects))
   678  	for _, v := range pos {
   679  		assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy())
   680  		assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell())
   681  		assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size())
   682  	}
   683  
   684  	// add a trade for a size of 3,
   685  	// potential buy should be 7, size 3
   686  	trade := types.Trade{
   687  		Type:      types.TradeTypeDefault,
   688  		ID:        "trade_i1",
   689  		MarketID:  "market_id",
   690  		Price:     num.NewUint(100),
   691  		Size:      3,
   692  		Buyer:     partyA,
   693  		Seller:    partyB,
   694  		BuyOrder:  "buy_order_id1",
   695  		SellOrder: "sell_order_id1",
   696  		Timestamp: time.Now().Unix(),
   697  	}
   698  
   699  	// add the trade
   700  	// call an update on the positions with the trade
   701  	positions := engine.Update(context.Background(), &trade, passive, aggressive)
   702  	pos = engine.Positions()
   703  	assert.Equal(t, 2, len(pos))
   704  	assert.Equal(t, 2, len(positions))
   705  
   706  	// check size of positions
   707  	for _, p := range pos {
   708  		if p.Party() == partyA {
   709  			assert.Equal(t, int64(3), p.Size())
   710  			assert.Equal(t, int64(7), p.Buy())
   711  			assert.Equal(t, cases.orders[0].Price, p.VWBuy())
   712  			assert.Equal(t, num.UintZero(), p.VWSell())
   713  		} else if p.Party() == partyB {
   714  			assert.Equal(t, int64(-3), p.Size())
   715  			assert.Equal(t, int64(13), p.Sell())
   716  			assert.Equal(t, num.UintZero(), p.VWBuy())
   717  			assert.Equal(t, cases.orders[len(cases.orders)-1].Price, p.VWSell())
   718  		}
   719  	}
   720  }
   721  
   722  func testNewTradePartialAmountOfIncomingOrderTraded(t *testing.T) {
   723  	engine := getTestEngine(t)
   724  	partyA := "party_a"
   725  	partyB := "party_b"
   726  	matchingPrice := num.NewUint(100)
   727  	tradeSize := uint64(3)
   728  
   729  	passive := &types.Order{
   730  		Size:      tradeSize,
   731  		Remaining: tradeSize,
   732  		Party:     partyB,
   733  		Side:      types.SideSell,
   734  		Price:     matchingPrice,
   735  	}
   736  
   737  	aggressive := &types.Order{
   738  		Size:      5 + tradeSize,
   739  		Remaining: 5 + tradeSize,
   740  		Party:     partyA,
   741  		Side:      types.SideBuy,
   742  		Price:     matchingPrice,
   743  	}
   744  
   745  	cases := struct {
   746  		orders  []*types.Order
   747  		expects map[string]struct {
   748  			expectedBuy    int64
   749  			expectedSell   int64
   750  			expectedSize   int64
   751  			expectedVwBuy  *num.Uint
   752  			expectedVwSell *num.Uint
   753  		}
   754  	}{
   755  		orders: []*types.Order{
   756  			{
   757  				Size:      16,
   758  				Remaining: 16,
   759  				Party:     partyB,
   760  				Side:      types.SideSell,
   761  				Price:     num.NewUint(1000),
   762  			},
   763  			passive,
   764  			aggressive,
   765  		},
   766  		expects: map[string]struct {
   767  			expectedBuy    int64
   768  			expectedSell   int64
   769  			expectedSize   int64
   770  			expectedVwBuy  *num.Uint
   771  			expectedVwSell *num.Uint
   772  		}{
   773  			partyA: {
   774  				expectedBuy:    8,
   775  				expectedSell:   0,
   776  				expectedSize:   0,
   777  				expectedVwBuy:  aggressive.Price,
   778  				expectedVwSell: num.UintZero(),
   779  			},
   780  			partyB: {
   781  				expectedBuy:    0,
   782  				expectedSell:   19,
   783  				expectedSize:   0,
   784  				expectedVwBuy:  num.UintOne(),
   785  				expectedVwSell: num.NewUint(857), // 857.8947368421
   786  			},
   787  		},
   788  	}
   789  
   790  	// no positions exists at the moment:
   791  	assert.Empty(t, engine.Positions())
   792  
   793  	for _, c := range cases.orders {
   794  		engine.RegisterOrder(context.TODO(), c)
   795  	}
   796  	pos := engine.Positions()
   797  	assert.Len(t, pos, len(cases.expects))
   798  	for _, v := range pos {
   799  		assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy())
   800  		assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell())
   801  		assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size())
   802  	}
   803  
   804  	// add a trade for a size of 3,
   805  	// potential buy should be 5, size 3
   806  	trade := types.Trade{
   807  		Type:      types.TradeTypeDefault,
   808  		ID:        "trade_i1",
   809  		MarketID:  "market_id",
   810  		Price:     num.NewUint(100),
   811  		Size:      3,
   812  		Buyer:     partyA,
   813  		Seller:    partyB,
   814  		BuyOrder:  "buy_order_id1",
   815  		SellOrder: "sell_order_id1",
   816  		Timestamp: time.Now().Unix(),
   817  	}
   818  
   819  	// add the trade
   820  	// call an update on the positions with the trade
   821  	positions := engine.Update(context.Background(), &trade, passive, aggressive)
   822  	pos = engine.Positions()
   823  	assert.Equal(t, 2, len(pos))
   824  	assert.Equal(t, 2, len(positions))
   825  
   826  	// check size of positions
   827  	for _, p := range pos {
   828  		if p.Party() == partyA {
   829  			assert.Equal(t, int64(3), p.Size())
   830  			assert.Equal(t, int64(5), p.Buy())
   831  			assert.Equal(t, matchingPrice, p.VWBuy())
   832  			assert.Equal(t, num.UintZero(), p.VWSell())
   833  		} else if p.Party() == partyB {
   834  			assert.Equal(t, int64(-3), p.Size())
   835  			assert.Equal(t, int64(16), p.Sell())
   836  			assert.Equal(t, num.UintZero(), p.VWBuy())
   837  			assert.Equal(t, cases.orders[0].Price, p.VWSell())
   838  		}
   839  	}
   840  }
   841  
   842  func testTradeCauseTheFullAmountOfOrderToTrade(t *testing.T) {
   843  	engine := getTestEngine(t)
   844  	partyA := "party_a"
   845  	partyB := "party_b"
   846  	cases := struct {
   847  		orders  []types.Order
   848  		expects map[string]struct {
   849  			expectedBuy  int64
   850  			expectedSell int64
   851  			expectedSize int64
   852  		}
   853  	}{
   854  		orders: []types.Order{
   855  			{
   856  				Size:      10,
   857  				Remaining: 10,
   858  				Party:     partyA,
   859  				Side:      types.SideBuy,
   860  				Price:     num.UintZero(),
   861  			},
   862  			{
   863  				Size:      10,
   864  				Remaining: 10,
   865  				Party:     partyB,
   866  				Side:      types.SideSell,
   867  				Price:     num.UintZero(),
   868  			},
   869  		},
   870  		expects: map[string]struct {
   871  			expectedBuy  int64
   872  			expectedSell int64
   873  			expectedSize int64
   874  		}{
   875  			partyA: {
   876  				expectedBuy:  10,
   877  				expectedSell: 0,
   878  				expectedSize: 0,
   879  			},
   880  			partyB: {
   881  				expectedBuy:  0,
   882  				expectedSell: 10,
   883  				expectedSize: 0,
   884  			},
   885  		},
   886  	}
   887  
   888  	// no potions exists at the moment:
   889  	assert.Empty(t, engine.Positions())
   890  
   891  	for i, c := range cases.orders {
   892  		engine.RegisterOrder(context.TODO(), &c)
   893  		// ensure we have 1 position with 1 potential buy of size 10 for partyA
   894  		pos := engine.Positions()
   895  		assert.Len(t, pos, i+1)
   896  		for _, v := range pos {
   897  			assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy())
   898  			assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell())
   899  			assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size())
   900  		}
   901  	}
   902  	// add a trade for a size of 3,
   903  	// potential buy should be 7, size 3
   904  	trade := types.Trade{
   905  		Type:      types.TradeTypeDefault,
   906  		ID:        "trade_i1",
   907  		MarketID:  "market_id",
   908  		Price:     num.NewUint(100),
   909  		Size:      10,
   910  		Buyer:     partyA,
   911  		Seller:    partyB,
   912  		BuyOrder:  "buy_order_id1",
   913  		SellOrder: "sell_order_id1",
   914  		Timestamp: time.Now().Unix(),
   915  	}
   916  
   917  	// add the trade
   918  	// call an update on the positions with the trade
   919  	positions := engine.Update(context.Background(), &trade, &cases.orders[0], &cases.orders[1])
   920  	pos := engine.Positions()
   921  	assert.Equal(t, 2, len(pos))
   922  	assert.Equal(t, 2, len(positions))
   923  
   924  	// check size of positions
   925  	for _, p := range pos {
   926  		if p.Party() == partyA {
   927  			assert.Equal(t, int64(10), p.Size())
   928  			assert.Equal(t, int64(0), p.Buy())
   929  		} else if p.Party() == partyB {
   930  			assert.Equal(t, int64(-10), p.Size())
   931  			assert.Equal(t, int64(0), p.Sell())
   932  		}
   933  	}
   934  }
   935  
   936  func testOrderCancelled(t *testing.T) {
   937  	engine := getTestEngine(t)
   938  	partyA := "party_a"
   939  	partyB := "party_b"
   940  	cases := struct {
   941  		orders  []types.Order
   942  		expects map[string]struct {
   943  			expectedBuy  int64
   944  			expectedSell int64
   945  			expectedSize int64
   946  		}
   947  	}{
   948  		orders: []types.Order{
   949  			{
   950  				Size:      10,
   951  				Remaining: 10,
   952  				Party:     partyA,
   953  				Side:      types.SideBuy,
   954  				Price:     num.UintZero(),
   955  			},
   956  			{
   957  				Size:      10,
   958  				Remaining: 10,
   959  				Party:     partyB,
   960  				Side:      types.SideSell,
   961  				Price:     num.UintZero(),
   962  			},
   963  		},
   964  		expects: map[string]struct {
   965  			expectedBuy  int64
   966  			expectedSell int64
   967  			expectedSize int64
   968  		}{
   969  			partyA: {
   970  				expectedBuy:  10,
   971  				expectedSell: 0,
   972  				expectedSize: 0,
   973  			},
   974  			partyB: {
   975  				expectedBuy:  0,
   976  				expectedSell: 10,
   977  				expectedSize: 0,
   978  			},
   979  		},
   980  	}
   981  
   982  	// no potions exists at the moment:
   983  	assert.Empty(t, engine.Positions())
   984  
   985  	// first add the orders
   986  	for i, c := range cases.orders {
   987  		engine.RegisterOrder(context.TODO(), &c)
   988  		// ensure we have 1 position with 1 potential buy of size 10 for partyA
   989  		pos := engine.Positions()
   990  		assert.Len(t, pos, i+1)
   991  		for _, v := range pos {
   992  			assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy())
   993  			assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell())
   994  			assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size())
   995  		}
   996  	}
   997  
   998  	// then remove them
   999  	cases = struct {
  1000  		orders  []types.Order
  1001  		expects map[string]struct {
  1002  			expectedBuy  int64
  1003  			expectedSell int64
  1004  			expectedSize int64
  1005  		}
  1006  	}{
  1007  		orders: []types.Order{
  1008  			{
  1009  				Size:      10,
  1010  				Remaining: 10,
  1011  				Party:     partyA,
  1012  				Side:      types.SideBuy,
  1013  				Price:     num.UintZero(),
  1014  			},
  1015  			{
  1016  				Size:      10,
  1017  				Remaining: 10,
  1018  				Party:     partyB,
  1019  				Side:      types.SideSell,
  1020  				Price:     num.UintZero(),
  1021  			},
  1022  		},
  1023  		expects: map[string]struct {
  1024  			expectedBuy  int64
  1025  			expectedSell int64
  1026  			expectedSize int64
  1027  		}{
  1028  			partyA: {
  1029  				expectedBuy:  0,
  1030  				expectedSell: 0,
  1031  				expectedSize: 0,
  1032  			},
  1033  			partyB: {
  1034  				expectedBuy:  0,
  1035  				expectedSell: 0,
  1036  				expectedSize: 0,
  1037  			},
  1038  		},
  1039  	}
  1040  
  1041  	// first add the orders
  1042  	for _, c := range cases.orders {
  1043  		_ = engine.UnregisterOrder(context.TODO(), &c)
  1044  	}
  1045  
  1046  	// test everything is back to 0 once orders are unregistered
  1047  	pos := engine.Positions()
  1048  	for _, v := range pos {
  1049  		assert.Equal(t, cases.expects[v.Party()].expectedBuy, v.Buy())
  1050  		assert.Equal(t, cases.expects[v.Party()].expectedSell, v.Sell())
  1051  		assert.Equal(t, cases.expects[v.Party()].expectedSize, v.Size())
  1052  	}
  1053  }