code.vegaprotocol.io/vega@v0.79.0/core/settlement/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 settlement_test
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"errors"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    27  	"code.vegaprotocol.io/vega/core/events"
    28  	"code.vegaprotocol.io/vega/core/settlement"
    29  	"code.vegaprotocol.io/vega/core/settlement/mocks"
    30  	"code.vegaprotocol.io/vega/core/types"
    31  	"code.vegaprotocol.io/vega/libs/num"
    32  	"code.vegaprotocol.io/vega/libs/proto"
    33  	"code.vegaprotocol.io/vega/logging"
    34  	snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    35  
    36  	"github.com/golang/mock/gomock"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  type testEngine struct {
    42  	*settlement.SnapshotEngine
    43  	ctrl      *gomock.Controller
    44  	prod      *mocks.MockProduct
    45  	positions []*mocks.MockMarketPosition
    46  	tsvc      *mocks.MockTimeService
    47  	broker    *bmocks.MockBroker
    48  	market    string
    49  }
    50  
    51  type posValue struct {
    52  	party string
    53  	price *num.Uint // absolute Mark price
    54  	size  int64
    55  }
    56  
    57  type marginVal struct {
    58  	events.MarketPosition
    59  	asset, marketID                               string
    60  	margin, orderMargin, general, marginShortFall uint64
    61  }
    62  
    63  func TestMarketExpiry(t *testing.T) {
    64  	t.Run("Settle at market expiry - success", testSettleExpiredSuccess)
    65  	t.Run("Settle at market expiry - error", testSettleExpiryFail)
    66  	t.Run("Settle at market expiry - rounding", testSettleRoundingSuccess)
    67  }
    68  
    69  func TestMarkToMarket(t *testing.T) {
    70  	t.Run("No settle positions if none were on channel", testMarkToMarketEmpty)
    71  	t.Run("Settle positions are pushed onto the slice channel in order", testMarkToMarketOrdered)
    72  	t.Run("Trade adds new party to market, no MTM settlement because markPrice is the same", testAddNewParty)
    73  	// add this test case because we had a runtime panic on the trades map earlier
    74  	t.Run("Trade adds new party, immediately closing out with themselves", testAddNewPartySelfTrade)
    75  	t.Run("Test MTM settle when the network is closed out", testMTMNetworkZero)
    76  	t.Run("Test settling a funding period", testSettlingAFundingPeriod)
    77  	t.Run("Test settling a funding period with rounding error", testSettlingAFundingPeriodRoundingError)
    78  	t.Run("Test settling a funding period with small win amounts (zero transfers)", testSettlingAFundingPeriodExcessSmallLoss)
    79  	t.Run("Test settling a funding period with rounding error (single party rounding)", testSettlingAFundingPeriodExcessLoss)
    80  	t.Run("Test settling a funding period with zero win amounts (loss exceeds 1)", testSettlingAFundingPeriodExcessLostExceedsOneLoss)
    81  }
    82  
    83  func TestMTMWinDistribution(t *testing.T) {
    84  	t.Run("A MTM loss party with a loss of value 1, with several parties needing a win", testMTMWinOneExcess)
    85  	t.Run("Distribute win excess in a scenario where no transfer amount is < 1", testMTMWinNoZero)
    86  	t.Run("Distribute loss excess in a scenario where no transfer amount is < 1", testMTMWinWithZero)
    87  	t.Run("A MTM case where win total > loss total", testMTMWinGTLoss)
    88  }
    89  
    90  func testSettlingAFundingPeriod(t *testing.T) {
    91  	engine := getTestEngine(t)
    92  	defer engine.Finish()
    93  	ctx := context.Background()
    94  
    95  	testPositions := []testPos{
    96  		{
    97  			party: "party1",
    98  			size:  10,
    99  		},
   100  		{
   101  			party: "party2",
   102  			size:  -10,
   103  		},
   104  		{
   105  			party: "party3",
   106  			size:  0,
   107  		},
   108  	}
   109  	positions := make([]events.MarketPosition, 0, len(testPositions))
   110  	for _, p := range testPositions {
   111  		positions = append(positions, p)
   112  	}
   113  
   114  	// 0 funding paymenet produces 0 transfers
   115  	transfers, round := engine.SettleFundingPeriod(ctx, positions, num.IntZero())
   116  	assert.Len(t, transfers, 0)
   117  	assert.Nil(t, round)
   118  
   119  	// no positions produces no transfers
   120  
   121  	// positive funding payement, shorts pay long
   122  	fundingPayment, _ := num.IntFromString("10", 10)
   123  	transfers, round = engine.SettleFundingPeriod(ctx, positions, fundingPayment)
   124  	require.Len(t, transfers, 2)
   125  	require.True(t, round.IsZero())
   126  	assert.Equal(t, "100", transfers[0].Transfer().Amount.Amount.String())
   127  	assert.Equal(t, types.TransferTypePerpFundingLoss, transfers[0].Transfer().Type)
   128  	assert.Equal(t, "100", transfers[1].Transfer().Amount.Amount.String())
   129  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[1].Transfer().Type)
   130  
   131  	// negative funding payement, long pays short, also expect loss to come before win
   132  	fundingPayment, _ = num.IntFromString("-10", 10)
   133  	transfers, round = engine.SettleFundingPeriod(ctx, positions, fundingPayment)
   134  	require.True(t, round.IsZero())
   135  	require.Len(t, transfers, 2)
   136  	assert.Equal(t, "100", transfers[0].Transfer().Amount.Amount.String())
   137  	assert.Equal(t, types.TransferTypePerpFundingLoss, transfers[0].Transfer().Type)
   138  	assert.Equal(t, "100", transfers[1].Transfer().Amount.Amount.String())
   139  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[1].Transfer().Type)
   140  
   141  	// no positions produces no transfers
   142  	transfers, round = engine.SettleFundingPeriod(ctx, []events.MarketPosition{}, fundingPayment)
   143  	require.Nil(t, round)
   144  	assert.Len(t, transfers, 0)
   145  }
   146  
   147  func testSettlingAFundingPeriodRoundingError(t *testing.T) {
   148  	engine := getTestEngineWithFactor(t, 100)
   149  	defer engine.Finish()
   150  	ctx := context.Background()
   151  
   152  	testPositions := []testPos{
   153  		{
   154  			party: "party1",
   155  			size:  1000010,
   156  		},
   157  		{
   158  			party: "party3",
   159  			size:  -1000005,
   160  		},
   161  		{
   162  			party: "party4",
   163  			size:  -1000005,
   164  		},
   165  	}
   166  
   167  	positions := make([]events.MarketPosition, 0, len(testPositions))
   168  	for _, p := range testPositions {
   169  		positions = append(positions, p)
   170  	}
   171  
   172  	fundingPayment, _ := num.IntFromString("10", 10)
   173  	transfers, round := engine.SettleFundingPeriod(ctx, positions, fundingPayment)
   174  	require.Len(t, transfers, 3)
   175  	assert.Equal(t, "100001", transfers[0].Transfer().Amount.Amount.String())
   176  	assert.Equal(t, types.TransferTypePerpFundingLoss, transfers[0].Transfer().Type)
   177  	assert.Equal(t, "100000", transfers[1].Transfer().Amount.Amount.String())
   178  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[1].Transfer().Type)
   179  	assert.Equal(t, "100000", transfers[2].Transfer().Amount.Amount.String())
   180  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[2].Transfer().Type)
   181  
   182  	// here 100001 will be sent to the settlement account, but only 200000 we be paid out due to rounding,
   183  	// so we expect a remainder of 1
   184  	require.Equal(t, "1", round.String())
   185  }
   186  
   187  func testSettlingAFundingPeriodExcessLostExceedsOneLoss(t *testing.T) {
   188  	engine := getTestEngineWithFactor(t, 100)
   189  	defer engine.Finish()
   190  	ctx := context.Background()
   191  
   192  	testPositions := []testPos{
   193  		{
   194  			party: "party1",
   195  			size:  23,
   196  		},
   197  		{
   198  			party: "party3",
   199  			size:  -3,
   200  		},
   201  		{
   202  			party: "party4",
   203  			size:  -5,
   204  		},
   205  		{
   206  			party: "party5",
   207  			size:  -5,
   208  		},
   209  		{
   210  			party: "party6",
   211  			size:  -5,
   212  		},
   213  		{
   214  			party: "party7",
   215  			size:  -5,
   216  		},
   217  	}
   218  
   219  	positions := make([]events.MarketPosition, 0, len(testPositions))
   220  	for _, p := range testPositions {
   221  		positions = append(positions, p)
   222  	}
   223  
   224  	fundingPayment, _ := num.IntFromString("10", 10)
   225  	transfers, round := engine.SettleFundingPeriod(ctx, positions, fundingPayment)
   226  	require.Len(t, transfers, 6)
   227  	assert.Equal(t, "2", transfers[0].Transfer().Amount.Amount.String())
   228  	assert.Equal(t, types.TransferTypePerpFundingLoss, transfers[0].Transfer().Type)
   229  	assert.Equal(t, "0", transfers[1].Transfer().Amount.Amount.String())
   230  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[1].Transfer().Type)
   231  	assert.Equal(t, "0", transfers[2].Transfer().Amount.Amount.String())
   232  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[2].Transfer().Type)
   233  	assert.Equal(t, "0", transfers[3].Transfer().Amount.Amount.String())
   234  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[3].Transfer().Type)
   235  	assert.Equal(t, "0", transfers[4].Transfer().Amount.Amount.String())
   236  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[4].Transfer().Type)
   237  	assert.Equal(t, "0", transfers[5].Transfer().Amount.Amount.String())
   238  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[5].Transfer().Type)
   239  
   240  	require.Equal(t, "2", round.String())
   241  }
   242  
   243  func testSettlingAFundingPeriodExcessSmallLoss(t *testing.T) {
   244  	engine := getTestEngineWithFactor(t, 100)
   245  	defer engine.Finish()
   246  	ctx := context.Background()
   247  
   248  	testPositions := []testPos{
   249  		{
   250  			party: "party1",
   251  			size:  11,
   252  		},
   253  		{
   254  			party: "party3",
   255  			size:  -3,
   256  		},
   257  		{
   258  			party: "party4",
   259  			size:  -3,
   260  		},
   261  		{
   262  			party: "party5",
   263  			size:  -5,
   264  		},
   265  	}
   266  
   267  	positions := make([]events.MarketPosition, 0, len(testPositions))
   268  	for _, p := range testPositions {
   269  		positions = append(positions, p)
   270  	}
   271  
   272  	fundingPayment, _ := num.IntFromString("10", 10)
   273  	transfers, round := engine.SettleFundingPeriod(ctx, positions, fundingPayment)
   274  	require.Len(t, transfers, 4)
   275  	assert.Equal(t, "1", transfers[0].Transfer().Amount.Amount.String())
   276  	assert.Equal(t, types.TransferTypePerpFundingLoss, transfers[0].Transfer().Type)
   277  	assert.Equal(t, "0", transfers[1].Transfer().Amount.Amount.String())
   278  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[1].Transfer().Type)
   279  	assert.Equal(t, "0", transfers[2].Transfer().Amount.Amount.String())
   280  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[2].Transfer().Type)
   281  	assert.Equal(t, "0", transfers[3].Transfer().Amount.Amount.String())
   282  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[3].Transfer().Type)
   283  
   284  	require.Equal(t, "1", round.String())
   285  }
   286  
   287  func testSettlingAFundingPeriodExcessLoss(t *testing.T) {
   288  	engine := getTestEngineWithFactor(t, 100)
   289  	defer engine.Finish()
   290  	ctx := context.Background()
   291  
   292  	testPositions := []testPos{
   293  		{
   294  			party: "party1",
   295  			size:  1000010,
   296  		},
   297  		{
   298  			party: "party3",
   299  			size:  -1000005,
   300  		},
   301  	}
   302  
   303  	positions := make([]events.MarketPosition, 0, len(testPositions))
   304  	for _, p := range testPositions {
   305  		positions = append(positions, p)
   306  	}
   307  
   308  	fundingPayment, _ := num.IntFromString("10", 10)
   309  	transfers, round := engine.SettleFundingPeriod(ctx, positions, fundingPayment)
   310  	require.Len(t, transfers, 2)
   311  	assert.Equal(t, "100001", transfers[0].Transfer().Amount.Amount.String())
   312  	assert.Equal(t, types.TransferTypePerpFundingLoss, transfers[0].Transfer().Type)
   313  	assert.Equal(t, "100000", transfers[1].Transfer().Amount.Amount.String())
   314  	assert.Equal(t, types.TransferTypePerpFundingWin, transfers[1].Transfer().Type)
   315  
   316  	// here 100001 will be sent to the settlement account, but only 200000 we be paid out due to rounding,
   317  	// so we expect a remainder of 1
   318  	require.Equal(t, "1", round.String())
   319  }
   320  
   321  func testMTMWinNoZero(t *testing.T) {
   322  	// cheat by setting the factor to some specific value, makes it easier to create a scenario where win/loss amounts don't match
   323  	engine := getTestEngineWithFactor(t, 1)
   324  	defer engine.Finish()
   325  
   326  	price := num.NewUint(100000)
   327  	one := num.NewUint(1)
   328  	ctx := context.Background()
   329  
   330  	initPos := []testPos{
   331  		{
   332  			price: price.Clone(),
   333  			party: "party1",
   334  			size:  10,
   335  		},
   336  		{
   337  			price: price.Clone(),
   338  			party: "party2",
   339  			size:  23,
   340  		},
   341  		{
   342  			price: price.Clone(),
   343  			party: "party3",
   344  			size:  -32,
   345  		},
   346  		{
   347  			price: price.Clone(),
   348  			party: "party4",
   349  			size:  1,
   350  		},
   351  		{
   352  			price: price.Clone(),
   353  			party: "party5",
   354  			size:  -29,
   355  		},
   356  		{
   357  			price: price.Clone(),
   358  			party: "party6",
   359  			size:  27,
   360  		},
   361  	}
   362  
   363  	init := make([]events.MarketPosition, 0, len(initPos))
   364  	for _, p := range initPos {
   365  		init = append(init, p)
   366  	}
   367  
   368  	newPrice := num.Sum(price, one, one, one)
   369  	somePrice := num.Sum(price, one)
   370  	newParty := testPos{
   371  		size:  30,
   372  		price: newPrice.Clone(),
   373  		party: "party4",
   374  	}
   375  
   376  	trades := []*types.Trade{
   377  		{
   378  			Size:   10,
   379  			Buyer:  newParty.party,
   380  			Seller: initPos[0].party,
   381  			Price:  somePrice.Clone(),
   382  		},
   383  		{
   384  			Size:   10,
   385  			Buyer:  newParty.party,
   386  			Seller: initPos[1].party,
   387  			Price:  somePrice.Clone(),
   388  		},
   389  		{
   390  			Size:   10,
   391  			Buyer:  newParty.party,
   392  			Seller: initPos[2].party,
   393  			Price:  newPrice.Clone(),
   394  		},
   395  	}
   396  	updates := make([]events.MarketPosition, 0, len(initPos)+2)
   397  	for _, trade := range trades {
   398  		for i, p := range initPos {
   399  			if p.party == trade.Seller {
   400  				p.size -= int64(trade.Size)
   401  			}
   402  			p.price = trade.Price.Clone()
   403  			initPos[i] = p
   404  		}
   405  	}
   406  	for _, p := range initPos {
   407  		updates = append(updates, p)
   408  	}
   409  	updates = append(updates, newParty)
   410  	engine.Update(init)
   411  	for _, trade := range trades {
   412  		engine.AddTrade(trade)
   413  	}
   414  	transfers := engine.SettleMTM(ctx, newPrice.Clone(), updates)
   415  	require.NotEmpty(t, transfers)
   416  }
   417  
   418  func testMTMWinOneExcess(t *testing.T) {
   419  	engine := getTestEngineWithFactor(t, 1)
   420  	defer engine.Finish()
   421  
   422  	price := num.NewUint(10000)
   423  	one := num.NewUint(1)
   424  	ctx := context.Background()
   425  
   426  	initPos := []testPos{
   427  		{
   428  			price: price.Clone(),
   429  			party: "party1",
   430  			size:  10,
   431  		},
   432  		{
   433  			price: price.Clone(),
   434  			party: "party2",
   435  			size:  20,
   436  		},
   437  		{
   438  			price: price.Clone(),
   439  			party: "party3",
   440  			size:  -29,
   441  		},
   442  		{
   443  			price: price.Clone(),
   444  			party: "party4",
   445  			size:  1,
   446  		},
   447  		{
   448  			price: price.Clone(),
   449  			party: "party5",
   450  			size:  -1,
   451  		},
   452  		{
   453  			price: price.Clone(),
   454  			party: "party5",
   455  			size:  1,
   456  		},
   457  	}
   458  
   459  	init := make([]events.MarketPosition, 0, len(initPos))
   460  	for _, p := range initPos {
   461  		init = append(init, p)
   462  	}
   463  
   464  	newPrice := num.Sum(price, one)
   465  	newParty := testPos{
   466  		size:  30,
   467  		price: newPrice.Clone(),
   468  		party: "party4",
   469  	}
   470  
   471  	trades := []*types.Trade{
   472  		{
   473  			Size:   10,
   474  			Buyer:  newParty.party,
   475  			Seller: initPos[0].party,
   476  			Price:  newPrice.Clone(),
   477  		},
   478  		{
   479  			Size:   10,
   480  			Buyer:  newParty.party,
   481  			Seller: initPos[1].party,
   482  			Price:  newPrice.Clone(),
   483  		},
   484  		{
   485  			Size:   10,
   486  			Buyer:  newParty.party,
   487  			Seller: initPos[2].party,
   488  			Price:  newPrice.Clone(),
   489  		},
   490  	}
   491  	updates := make([]events.MarketPosition, 0, len(initPos)+2)
   492  	for _, trade := range trades {
   493  		for i, p := range initPos {
   494  			if p.party == trade.Seller {
   495  				p.size -= int64(trade.Size)
   496  			}
   497  			p.price = trade.Price.Clone()
   498  			initPos[i] = p
   499  		}
   500  	}
   501  	for _, p := range initPos {
   502  		updates = append(updates, p)
   503  	}
   504  	updates = append(updates, newParty)
   505  	engine.Update(init)
   506  	for _, trade := range trades {
   507  		engine.AddTrade(trade)
   508  	}
   509  	transfers := engine.SettleMTM(ctx, newPrice.Clone(), updates)
   510  	require.NotEmpty(t, transfers)
   511  }
   512  
   513  func testSettleRoundingSuccess(t *testing.T) {
   514  	engine := getTestEngineWithFactor(t, 10)
   515  	defer engine.Finish()
   516  	// these are mark prices, product will provide the actual value
   517  	// total wins = 554, total losses = 555
   518  	pr := num.NewUint(1000)
   519  	data := []posValue{
   520  		{
   521  			party: "party1",
   522  			price: pr,   // winning
   523  			size:  1101, // 5 * 110.1 = 550.5 -> 550
   524  		},
   525  		{
   526  			party: "party2",
   527  			price: pr,   // losing
   528  			size:  -550, // 5 * 55 = 275
   529  		},
   530  		{
   531  			party: "party3",
   532  			price: pr,   // losing
   533  			size:  -560, // 5 * 56 = 280
   534  		},
   535  		{
   536  			party: "party4",
   537  			price: pr, // winning
   538  			size:  9,  // 5 * .9 = 4.5-> 4
   539  		},
   540  	}
   541  	expect := []*types.Transfer{
   542  		{
   543  			Owner: data[1].party,
   544  			Amount: &types.FinancialAmount{
   545  				Amount: num.NewUint(275),
   546  			},
   547  			Type: types.TransferTypeLoss,
   548  		},
   549  		{
   550  			Owner: data[2].party,
   551  			Amount: &types.FinancialAmount{
   552  				Amount: num.NewUint(280),
   553  			},
   554  			Type: types.TransferTypeLoss,
   555  		},
   556  		{
   557  			Owner: data[0].party,
   558  			Amount: &types.FinancialAmount{
   559  				Amount: num.NewUint(550),
   560  			},
   561  			Type: types.TransferTypeWin,
   562  		},
   563  		{
   564  			Owner: data[3].party,
   565  			Amount: &types.FinancialAmount{
   566  				Amount: num.NewUint(4),
   567  			},
   568  			Type: types.TransferTypeWin,
   569  		},
   570  	}
   571  	oraclePrice := num.NewUint(1005)
   572  	settleF := func(price *num.Uint, settlementData *num.Uint, size num.Decimal) (*types.FinancialAmount, bool, num.Decimal, error) {
   573  		amt, neg := num.UintZero().Delta(oraclePrice, price)
   574  		if size.IsNegative() {
   575  			size = size.Neg()
   576  			neg = !neg
   577  		}
   578  
   579  		amount, rem := num.UintFromDecimalWithFraction(amt.ToDecimal().Mul(size))
   580  		return &types.FinancialAmount{
   581  			Amount: amount,
   582  		}, neg, rem, nil
   583  	}
   584  	positions := engine.getExpiryPositions(data...)
   585  	// we expect settle calls for each position
   586  	engine.prod.EXPECT().Settle(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(settleF).AnyTimes()
   587  	// ensure positions are set
   588  	engine.Update(positions)
   589  	// now settle:
   590  	got, round, err := engine.Settle(time.Now(), oraclePrice)
   591  	assert.NoError(t, err)
   592  	assert.Equal(t, len(expect), len(got))
   593  	assert.True(t, round.EQ(num.NewUint(1)))
   594  	for i, p := range got {
   595  		e := expect[i]
   596  		assert.Equal(t, e.Type, p.Type)
   597  		assert.Equal(t, e.Amount.Amount, p.Amount.Amount)
   598  	}
   599  }
   600  
   601  func testSettleExpiredSuccess(t *testing.T) {
   602  	engine := getTestEngine(t)
   603  	defer engine.Finish()
   604  	// these are mark prices, product will provide the actual value
   605  	pr := num.NewUint(1000)
   606  	data := []posValue{ // {{{2
   607  		{
   608  			party: "party1",
   609  			price: pr, // winning
   610  			size:  10,
   611  		},
   612  		{
   613  			party: "party2",
   614  			price: pr, // losing
   615  			size:  -5,
   616  		},
   617  		{
   618  			party: "party3",
   619  			price: pr, // losing
   620  			size:  -5,
   621  		},
   622  	}
   623  	half := num.NewUint(500)
   624  	expect := []*types.Transfer{
   625  		{
   626  			Owner: data[1].party,
   627  			Amount: &types.FinancialAmount{
   628  				Amount: half,
   629  			},
   630  			Type: types.TransferTypeLoss,
   631  		},
   632  		{
   633  			Owner: data[2].party,
   634  			Amount: &types.FinancialAmount{
   635  				Amount: half,
   636  			},
   637  			Type: types.TransferTypeLoss,
   638  		},
   639  		{
   640  			Owner: data[0].party,
   641  			Amount: &types.FinancialAmount{
   642  				Amount: pr,
   643  			},
   644  			Type: types.TransferTypeWin,
   645  		},
   646  	} // }}}
   647  	oraclePrice := num.NewUint(1100)
   648  	settleF := func(price *num.Uint, settlementData *num.Uint, size num.Decimal) (*types.FinancialAmount, bool, num.Decimal, error) {
   649  		amt, neg := num.UintZero().Delta(oraclePrice, price)
   650  		if size.IsNegative() {
   651  			size = size.Neg()
   652  			neg = !neg
   653  		}
   654  
   655  		amount, rem := num.UintFromDecimalWithFraction(amt.ToDecimal().Mul(size))
   656  		return &types.FinancialAmount{
   657  			Amount: amount,
   658  		}, neg, rem, nil
   659  	}
   660  	positions := engine.getExpiryPositions(data...)
   661  	// we expect settle calls for each position
   662  	engine.prod.EXPECT().Settle(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(settleF).AnyTimes()
   663  	// ensure positions are set
   664  	engine.Update(positions)
   665  	// now settle:
   666  	got, _, err := engine.Settle(time.Now(), oraclePrice)
   667  	assert.NoError(t, err)
   668  	assert.Equal(t, len(expect), len(got))
   669  	for i, p := range got {
   670  		e := expect[i]
   671  		assert.Equal(t, e.Type, p.Type)
   672  		assert.Equal(t, e.Amount.Amount, p.Amount.Amount)
   673  	}
   674  }
   675  
   676  func testSettleExpiryFail(t *testing.T) {
   677  	engine := getTestEngine(t)
   678  	defer engine.Finish()
   679  	// these are mark prices, product will provide the actual value
   680  	data := []posValue{
   681  		{
   682  			party: "party1",
   683  			price: num.NewUint(1000),
   684  			size:  10,
   685  		},
   686  	}
   687  	errExp := errors.New("product.Settle error")
   688  	positions := engine.getExpiryPositions(data...)
   689  	engine.prod.EXPECT().Settle(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil, false, num.DecimalZero(), errExp)
   690  	engine.Update(positions)
   691  	empty, _, err := engine.Settle(time.Now(), num.UintZero())
   692  	assert.Empty(t, empty)
   693  	assert.Error(t, err)
   694  	assert.Equal(t, errExp, err)
   695  }
   696  
   697  func testMarkToMarketEmpty(t *testing.T) {
   698  	markPrice := num.NewUint(10000)
   699  	// there's only 1 trade to test here
   700  	data := posValue{
   701  		price: markPrice,
   702  		size:  1,
   703  		party: "test",
   704  	}
   705  	engine := getTestEngine(t)
   706  	defer engine.Finish()
   707  	pos := mocks.NewMockMarketPosition(engine.ctrl)
   708  	pos.EXPECT().Party().AnyTimes().Return(data.party)
   709  	pos.EXPECT().Size().AnyTimes().Return(data.size)
   710  	pos.EXPECT().Price().AnyTimes().Return(markPrice)
   711  	engine.Update([]events.MarketPosition{pos})
   712  	result := engine.SettleMTM(context.Background(), markPrice, []events.MarketPosition{pos})
   713  	assert.Empty(t, result)
   714  }
   715  
   716  func testAddNewPartySelfTrade(t *testing.T) {
   717  	engine := getTestEngine(t)
   718  	defer engine.Finish()
   719  	markPrice := num.NewUint(1000)
   720  	t1 := testPos{
   721  		price: markPrice.Clone(),
   722  		party: "party1",
   723  		size:  5,
   724  	}
   725  	init := []events.MarketPosition{
   726  		t1,
   727  		testPos{
   728  			price: markPrice.Clone(),
   729  			party: "party2",
   730  			size:  -5,
   731  		},
   732  	}
   733  	// let's not change the markPrice
   734  	// just add a party to the market, buying from an existing party
   735  	trade := &types.Trade{
   736  		Buyer:  "party3",
   737  		Seller: "party3",
   738  		Price:  markPrice.Clone(),
   739  		Size:   1,
   740  	}
   741  	// the first party is the seller
   742  	// so these are the new positions after the trade
   743  	t1.size--
   744  	positions := []events.MarketPosition{
   745  		t1,
   746  		init[1],
   747  		testPos{
   748  			party: "party3",
   749  			size:  0,
   750  			price: markPrice.Clone(),
   751  		},
   752  	}
   753  	engine.Update(init)
   754  	engine.AddTrade(trade)
   755  	noTransfers := engine.SettleMTM(context.Background(), markPrice, positions)
   756  	assert.Len(t, noTransfers, 1)
   757  	assert.Nil(t, noTransfers[0].Transfer())
   758  }
   759  
   760  func TestNetworkPartyCloseout(t *testing.T) {
   761  	engine := getTestEngine(t)
   762  	currentMP := num.NewUint(1000)
   763  	// network trade
   764  	nTrade := &types.Trade{
   765  		Buyer:       types.NetworkParty,
   766  		Seller:      types.NetworkParty,
   767  		Price:       currentMP.Clone(),
   768  		MarketPrice: currentMP.Clone(),
   769  		Size:        10,
   770  	}
   771  	nPosition := testPos{
   772  		party: types.NetworkParty,
   773  		size:  10,
   774  		price: currentMP.Clone(),
   775  	}
   776  	sellPrice := num.NewUint(990)
   777  	// now trade with health party, at some different price
   778  	// trigger a loss for the network.
   779  	cTrade := &types.Trade{
   780  		Buyer:  "party1",
   781  		Seller: types.NetworkParty,
   782  		Size:   2,
   783  		Price:  sellPrice.Clone(),
   784  	}
   785  	init := []events.MarketPosition{
   786  		nPosition,
   787  		testPos{
   788  			party: "party1",
   789  			size:  0,
   790  			price: currentMP.Clone(),
   791  		},
   792  	}
   793  	positions := []events.MarketPosition{
   794  		testPos{
   795  			party: types.NetworkParty,
   796  			size:  8,
   797  			price: currentMP.Clone(),
   798  		},
   799  		testPos{
   800  			party: "party1",
   801  			size:  2,
   802  			price: currentMP.Clone(),
   803  		},
   804  	}
   805  	engine.Update(init)
   806  	engine.AddTrade(nTrade)
   807  	engine.AddTrade(cTrade)
   808  	transfers := engine.SettleMTM(context.Background(), currentMP, positions)
   809  	assert.NotEmpty(t, transfers)
   810  	assert.Len(t, transfers, 2)
   811  }
   812  
   813  func TestNetworkCloseoutZero(t *testing.T) {
   814  	engine := getTestEngine(t)
   815  	currentMP := num.NewUint(1000)
   816  	// network trade
   817  	nTrade := &types.Trade{
   818  		Buyer:       types.NetworkParty,
   819  		Seller:      types.NetworkParty,
   820  		Price:       currentMP.Clone(),
   821  		MarketPrice: currentMP.Clone(),
   822  		Size:        10,
   823  	}
   824  	nPosition := testPos{
   825  		party: types.NetworkParty,
   826  		size:  10,
   827  		price: currentMP.Clone(),
   828  	}
   829  	sellPrice := num.NewUint(999)
   830  	// now trade with health party, at some different price
   831  	// trigger a loss for the network.
   832  	cTrades := []*types.Trade{
   833  		{
   834  			Buyer:  "party1",
   835  			Seller: types.NetworkParty,
   836  			Size:   1,
   837  			Price:  sellPrice.Clone(),
   838  		},
   839  		{
   840  			Buyer:  "party2",
   841  			Seller: types.NetworkParty,
   842  			Size:   1,
   843  			Price:  sellPrice.Clone(),
   844  		},
   845  	}
   846  	init := []events.MarketPosition{
   847  		nPosition,
   848  		testPos{
   849  			party: "party1",
   850  			size:  0,
   851  			price: currentMP.Clone(),
   852  		},
   853  		testPos{
   854  			party: "party2",
   855  			size:  0,
   856  			price: currentMP.Clone(),
   857  		},
   858  		testPos{
   859  			party: "party3",
   860  			size:  -5,
   861  			price: currentMP.Clone(),
   862  		},
   863  		testPos{
   864  			party: "party4",
   865  			size:  -5,
   866  			price: currentMP.Clone(),
   867  		},
   868  	}
   869  	positions := []events.MarketPosition{
   870  		testPos{
   871  			party: types.NetworkParty,
   872  			size:  8,
   873  			price: currentMP.Clone(),
   874  		},
   875  		testPos{
   876  			party: "party1",
   877  			size:  1,
   878  			price: currentMP.Clone(),
   879  		},
   880  		testPos{
   881  			party: "party2",
   882  			size:  1,
   883  			price: currentMP.Clone(),
   884  		},
   885  		testPos{
   886  			party: "party3",
   887  			size:  -5,
   888  			price: currentMP.Clone(),
   889  		},
   890  		testPos{
   891  			party: "party4",
   892  			size:  -5,
   893  			price: currentMP.Clone(),
   894  		},
   895  	}
   896  	engine.Update(init)
   897  	engine.AddTrade(nTrade)
   898  	for _, cTrade := range cTrades {
   899  		engine.AddTrade(cTrade)
   900  	}
   901  	transfers := engine.SettleMTM(context.Background(), currentMP, positions)
   902  	assert.NotEmpty(t, transfers)
   903  	// now that the network has an established long position, make short positions close out and mark to market
   904  	// party 3 closes their position, lowering the mark price
   905  	newMP := num.NewUint(990)
   906  	trade := &types.Trade{
   907  		Buyer:  "party3",
   908  		Seller: "party1",
   909  		Price:  newMP,
   910  		Size:   1,
   911  	}
   912  	positions = []events.MarketPosition{
   913  		testPos{
   914  			party: types.NetworkParty,
   915  			size:  8,
   916  			price: newMP.Clone(),
   917  		},
   918  		testPos{
   919  			party: "party1",
   920  			size:  0,
   921  			price: newMP.Clone(),
   922  		},
   923  		testPos{
   924  			party: "party2",
   925  			size:  1,
   926  			price: newMP.Clone(),
   927  		},
   928  		testPos{
   929  			party: "party3",
   930  			size:  -4,
   931  			price: newMP.Clone(),
   932  		},
   933  		testPos{
   934  			party: "party4",
   935  			size:  -5,
   936  			price: newMP.Clone(),
   937  		},
   938  	}
   939  	engine.AddTrade(trade)
   940  	transfers = engine.SettleMTM(context.Background(), newMP, positions)
   941  	assert.NotEmpty(t, transfers)
   942  	// now make it look like party2 got distressed because of this MTM settlement
   943  	nTrade = &types.Trade{
   944  		Price:       newMP.Clone(),
   945  		MarketPrice: newMP.Clone(),
   946  		Size:        1,
   947  		Buyer:       types.NetworkParty,
   948  		Seller:      types.NetworkParty,
   949  	}
   950  	positions = []events.MarketPosition{
   951  		testPos{
   952  			party: types.NetworkParty,
   953  			size:  9,
   954  			price: newMP.Clone(),
   955  		},
   956  		testPos{
   957  			party: "party3",
   958  			size:  -4,
   959  			price: newMP.Clone(),
   960  		},
   961  		testPos{
   962  			party: "party4",
   963  			size:  -5,
   964  			price: newMP.Clone(),
   965  		},
   966  	}
   967  	engine.AddTrade(nTrade)
   968  	transfers = engine.SettleMTM(context.Background(), newMP, positions)
   969  	assert.NotEmpty(t, transfers)
   970  	newMP = num.NewUint(995)
   971  	positions = []events.MarketPosition{
   972  		testPos{
   973  			party: types.NetworkParty,
   974  			size:  9,
   975  			price: newMP.Clone(),
   976  		},
   977  		testPos{
   978  			party: "party3",
   979  			size:  -4,
   980  			price: newMP.Clone(),
   981  		},
   982  		testPos{
   983  			party: "party4",
   984  			size:  -5,
   985  			price: newMP.Clone(),
   986  		},
   987  	}
   988  	transfers = engine.SettleMTM(context.Background(), newMP.Clone(), positions)
   989  	assert.NotEmpty(t, transfers)
   990  	// now the same, but network loses
   991  	newMP = num.NewUint(990)
   992  	positions = []events.MarketPosition{
   993  		testPos{
   994  			party: types.NetworkParty,
   995  			size:  9,
   996  			price: newMP.Clone(),
   997  		},
   998  		testPos{
   999  			party: "party3",
  1000  			size:  -4,
  1001  			price: newMP.Clone(),
  1002  		},
  1003  		testPos{
  1004  			party: "party4",
  1005  			size:  -5,
  1006  			price: newMP.Clone(),
  1007  		},
  1008  	}
  1009  	transfers = engine.SettleMTM(context.Background(), newMP.Clone(), positions)
  1010  	assert.NotEmpty(t, transfers)
  1011  	// assume no trades occurred, but the mark price has changed (shouldn't happen, but this could end up with a situation where network profits without trading)
  1012  	// network disposes of its position and profits
  1013  	disposePrice := num.NewUint(1010)
  1014  	trades := []*types.Trade{
  1015  		{
  1016  			Seller: types.NetworkParty,
  1017  			Buyer:  "party3",
  1018  			Price:  disposePrice.Clone(),
  1019  			Size:   4,
  1020  		},
  1021  		{
  1022  			Seller: types.NetworkParty,
  1023  			Buyer:  "party4",
  1024  			Price:  disposePrice.Clone(),
  1025  			Size:   5,
  1026  		},
  1027  	}
  1028  	positions = []events.MarketPosition{
  1029  		testPos{
  1030  			party: types.NetworkParty,
  1031  			size:  0,
  1032  			price: newMP.Clone(),
  1033  		},
  1034  		testPos{
  1035  			party: "party3",
  1036  			size:  0,
  1037  			price: newMP.Clone(),
  1038  		},
  1039  		testPos{
  1040  			party: "party4",
  1041  			size:  0,
  1042  			price: newMP.Clone(),
  1043  		},
  1044  	}
  1045  	for _, tr := range trades {
  1046  		engine.AddTrade(tr)
  1047  	}
  1048  	transfers = engine.SettleMTM(context.Background(), num.NewUint(1000), positions)
  1049  	assert.NotEmpty(t, transfers)
  1050  }
  1051  
  1052  func testAddNewParty(t *testing.T) {
  1053  	engine := getTestEngine(t)
  1054  	defer engine.Finish()
  1055  	markPrice := num.NewUint(1000)
  1056  	t1 := testPos{
  1057  		price: markPrice.Clone(),
  1058  		party: "party1",
  1059  		size:  5,
  1060  	}
  1061  	init := []events.MarketPosition{
  1062  		t1,
  1063  		testPos{
  1064  			price: markPrice.Clone(),
  1065  			party: "party2",
  1066  			size:  -5,
  1067  		},
  1068  	}
  1069  	// let's not change the markPrice
  1070  	// just add a party to the market, buying from an existing party
  1071  	trade := &types.Trade{
  1072  		Buyer:  "party3",
  1073  		Seller: t1.party,
  1074  		Price:  markPrice.Clone(),
  1075  		Size:   1,
  1076  	}
  1077  	// the first party is the seller
  1078  	// so these are the new positions after the trade
  1079  	t1.size--
  1080  	positions := []events.MarketPosition{
  1081  		t1,
  1082  		init[1],
  1083  		testPos{
  1084  			party: "party3",
  1085  			size:  1,
  1086  			price: markPrice.Clone(),
  1087  		},
  1088  	}
  1089  	engine.Update(init)
  1090  	engine.AddTrade(trade)
  1091  	noTransfers := engine.SettleMTM(context.Background(), markPrice, positions)
  1092  	assert.Len(t, noTransfers, 2)
  1093  	for _, v := range noTransfers {
  1094  		assert.Nil(t, v.Transfer())
  1095  	}
  1096  }
  1097  
  1098  // This tests MTM results put losses first, trades tested are Long going longer, short going shorter
  1099  // and long going short, short going long, and a third party who's not trading at all.
  1100  func testMarkToMarketOrdered(t *testing.T) {
  1101  	engine := getTestEngine(t)
  1102  	defer engine.Finish()
  1103  	pr := num.NewUint(10000)
  1104  	positions := []posValue{
  1105  		{
  1106  			price: pr,
  1107  			size:  1,
  1108  			party: "party1", // mocks will create 2 parties (long & short)
  1109  		},
  1110  		{
  1111  			price: pr.Clone(),
  1112  			size:  -1,
  1113  			party: "party2",
  1114  		},
  1115  	}
  1116  	markPrice := pr.Clone()
  1117  	markPrice = markPrice.Add(markPrice, num.NewUint(1000))
  1118  	neutral := testPos{
  1119  		party: "neutral",
  1120  		size:  5,
  1121  		price: pr.Clone(),
  1122  	}
  1123  	init := []events.MarketPosition{
  1124  		neutral,
  1125  		testPos{
  1126  			price: neutral.price.Clone(),
  1127  			party: "party1",
  1128  			size:  1,
  1129  		},
  1130  		testPos{
  1131  			price: neutral.price.Clone(),
  1132  			party: "party2",
  1133  			size:  -1,
  1134  		},
  1135  	}
  1136  	short, long := make([]events.MarketPosition, 0, 3), make([]events.MarketPosition, 0, 3)
  1137  	// the SettleMTM data must contain the new mark price already
  1138  	neutral.price = markPrice.Clone()
  1139  	short = append(short, neutral)
  1140  	long = append(long, neutral)
  1141  	// we have a long and short trade example
  1142  	trades := map[string]*types.Trade{
  1143  		"long": {
  1144  			Price: markPrice,
  1145  			Size:  1,
  1146  		},
  1147  		// to go short, the trade has to be 2
  1148  		"short": {
  1149  			Price: markPrice,
  1150  			Size:  2,
  1151  		},
  1152  	}
  1153  	// creates trades and event slices we'll be needing later on
  1154  	for _, p := range positions {
  1155  		if p.size > 0 {
  1156  			trades["long"].Buyer = p.party
  1157  			trades["short"].Seller = p.party
  1158  			long = append(long, testPos{
  1159  				party: p.party,
  1160  				price: markPrice.Clone(),
  1161  				size:  p.size + int64(trades["long"].Size),
  1162  			})
  1163  			short = append(short, testPos{
  1164  				party: p.party,
  1165  				price: markPrice.Clone(),
  1166  				size:  p.size - int64(trades["short"].Size),
  1167  			})
  1168  		} else {
  1169  			trades["long"].Seller = p.party
  1170  			trades["short"].Buyer = p.party
  1171  			long = append(long, testPos{
  1172  				party: p.party,
  1173  				price: markPrice.Clone(),
  1174  				size:  p.size - int64(trades["long"].Size),
  1175  			})
  1176  			short = append(short, testPos{
  1177  				party: p.party,
  1178  				price: markPrice.Clone(),
  1179  				size:  p.size + int64(trades["short"].Size),
  1180  			})
  1181  		}
  1182  	}
  1183  	updates := map[string][]events.MarketPosition{
  1184  		"long":  long,
  1185  		"short": short,
  1186  	}
  1187  	// set up the engine, ready to run the scenario's
  1188  	// for each data-set we reset the state in the engine, then we check the MTM is performed
  1189  	// correctly
  1190  	for k, trade := range trades {
  1191  		engine.Update(init)
  1192  		engine.AddTrade(trade)
  1193  		update := updates[k]
  1194  		transfers := engine.SettleMTM(context.Background(), markPrice, update)
  1195  		assert.NotEmpty(t, transfers)
  1196  		assert.Equal(t, 3, len(transfers))
  1197  		// start with losses, end with wins
  1198  		assert.Equal(t, types.TransferTypeMTMLoss, transfers[0].Transfer().Type)
  1199  		assert.Equal(t, types.TransferTypeMTMWin, transfers[len(transfers)-1].Transfer().Type)
  1200  		assert.Equal(t, "party2", transfers[0].Party()) // we expect party2 to have a loss
  1201  	}
  1202  
  1203  	state, _, _ := engine.GetState(engine.market)
  1204  	engineLoad := getTestEngine(t)
  1205  	var pl snapshot.Payload
  1206  	require.NoError(t, proto.Unmarshal(state, &pl))
  1207  	payload := types.PayloadFromProto(&pl)
  1208  
  1209  	_, err := engineLoad.LoadState(context.Background(), payload)
  1210  	require.NoError(t, err)
  1211  
  1212  	state2, _, _ := engineLoad.GetState(engine.market)
  1213  	require.True(t, bytes.Equal(state, state2))
  1214  }
  1215  
  1216  func testMTMNetworkZero(t *testing.T) {
  1217  	t.Skip("not implemented yet")
  1218  	engine := getTestEngine(t)
  1219  	defer engine.Finish()
  1220  	markPrice := num.NewUint(1000)
  1221  	init := []events.MarketPosition{
  1222  		testPos{
  1223  			price: markPrice.Clone(),
  1224  			party: "party1",
  1225  			size:  5,
  1226  		},
  1227  		testPos{
  1228  			price: markPrice.Clone(),
  1229  			party: "party2",
  1230  			size:  -5,
  1231  		},
  1232  		testPos{
  1233  			price: markPrice.Clone(),
  1234  			party: "party3",
  1235  			size:  10,
  1236  		},
  1237  		testPos{
  1238  			price: markPrice.Clone(),
  1239  			party: "party4",
  1240  			size:  -10,
  1241  		},
  1242  	}
  1243  	// initialise the engine with the positions above
  1244  	engine.Update(init)
  1245  	// assume party 4 is distressed, network has to trade and buy 10
  1246  	// ensure the network loses in this scenario: the price has gone up
  1247  	cPrice := num.Sum(markPrice, num.NewUint(1))
  1248  	trade := &types.Trade{
  1249  		Buyer:  types.NetworkParty,
  1250  		Seller: "party1",
  1251  		Size:   5, // party 1 only has 5 on the book, let's pretend we can close him our
  1252  		Price:  cPrice.Clone(),
  1253  	}
  1254  	engine.AddTrade(trade)
  1255  	engine.AddTrade(&types.Trade{
  1256  		Buyer:  types.NetworkParty,
  1257  		Seller: "party3",
  1258  		Size:   2,
  1259  		Price:  cPrice.Clone(),
  1260  	})
  1261  	engine.AddTrade(&types.Trade{
  1262  		Buyer:  types.NetworkParty,
  1263  		Seller: "party2",
  1264  		Size:   3,
  1265  		Price:  cPrice.Clone(), // party 2 is going from -5 to -8
  1266  	})
  1267  	// the new positions of the parties who have traded with the network...
  1268  	positions := []events.MarketPosition{
  1269  		testPos{
  1270  			party: "party1", // party 1 was 5 long, sold 5 to network, so closed out
  1271  			price: markPrice.Clone(),
  1272  			size:  0,
  1273  		},
  1274  		testPos{
  1275  			party: "party3",
  1276  			size:  8, // long 10, sold 2
  1277  			price: markPrice.Clone(),
  1278  		},
  1279  		testPos{
  1280  			party: "party2",
  1281  			size:  -8,
  1282  			price: markPrice.Clone(), // party 2 was -5, shorted an additional 3 => -8
  1283  		},
  1284  	}
  1285  	// new markprice is cPrice
  1286  	noTransfers := engine.SettleMTM(context.Background(), cPrice, positions)
  1287  	assert.Len(t, noTransfers, 3)
  1288  	hasNetwork := false
  1289  	for i, v := range noTransfers {
  1290  		assert.NotNil(t, v.Transfer())
  1291  		if v.Party() == types.NetworkParty {
  1292  			// network hás to lose
  1293  			require.Equal(t, types.TransferTypeMTMLoss, v.Transfer().Type)
  1294  			// network loss should be at the start of the slice
  1295  			require.Equal(t, 0, i)
  1296  			hasNetwork = true
  1297  		}
  1298  	}
  1299  	require.True(t, hasNetwork)
  1300  }
  1301  
  1302  func testMTMWinWithZero(t *testing.T) {
  1303  	// cheat by setting the factor to some specific value, makes it easier to create a scenario where win/loss amounts don't match
  1304  	engine := getTestEngineWithFactor(t, 5)
  1305  	defer engine.Finish()
  1306  
  1307  	price := num.NewUint(100000)
  1308  	one := num.NewUint(1)
  1309  	ctx := context.Background()
  1310  
  1311  	initPos := []testPos{
  1312  		{
  1313  			price: price.Clone(),
  1314  			party: "party1",
  1315  			size:  1,
  1316  		},
  1317  		{
  1318  			price: price.Clone(),
  1319  			party: "party2",
  1320  			size:  2,
  1321  		},
  1322  		{
  1323  			price: price.Clone(),
  1324  			party: "party3",
  1325  			size:  -3,
  1326  		},
  1327  		{
  1328  			price: price.Clone(),
  1329  			party: "party4",
  1330  			size:  1,
  1331  		},
  1332  		{
  1333  			price: price.Clone(),
  1334  			party: "party5",
  1335  			size:  -3,
  1336  		},
  1337  		{
  1338  			price: price.Clone(),
  1339  			party: "party6",
  1340  			size:  2,
  1341  		},
  1342  	}
  1343  
  1344  	init := make([]events.MarketPosition, 0, len(initPos))
  1345  	for _, p := range initPos {
  1346  		init = append(init, p)
  1347  	}
  1348  
  1349  	newPrice := num.Sum(price, one, one, one)
  1350  	somePrice := num.Sum(price, one)
  1351  	newParty := testPos{
  1352  		size:  3,
  1353  		price: newPrice.Clone(),
  1354  		party: "party4",
  1355  	}
  1356  
  1357  	trades := []*types.Trade{
  1358  		{
  1359  			Size:   1,
  1360  			Buyer:  newParty.party,
  1361  			Seller: initPos[0].party,
  1362  			Price:  somePrice.Clone(),
  1363  		},
  1364  		{
  1365  			Size:   1,
  1366  			Buyer:  newParty.party,
  1367  			Seller: initPos[1].party,
  1368  			Price:  somePrice.Clone(),
  1369  		},
  1370  		{
  1371  			Size:   1,
  1372  			Buyer:  newParty.party,
  1373  			Seller: initPos[2].party,
  1374  			Price:  newPrice.Clone(),
  1375  		},
  1376  	}
  1377  	updates := make([]events.MarketPosition, 0, len(initPos)+2)
  1378  	for _, trade := range trades {
  1379  		for i, p := range initPos {
  1380  			if p.party == trade.Seller {
  1381  				p.size -= int64(trade.Size)
  1382  			}
  1383  			p.price = trade.Price.Clone()
  1384  			initPos[i] = p
  1385  		}
  1386  	}
  1387  	for _, p := range initPos {
  1388  		updates = append(updates, p)
  1389  	}
  1390  	updates = append(updates, newParty)
  1391  	engine.Update(init)
  1392  	for _, trade := range trades {
  1393  		engine.AddTrade(trade)
  1394  	}
  1395  	transfers := engine.SettleMTM(ctx, newPrice.Clone(), updates)
  1396  	require.NotEmpty(t, transfers)
  1397  }
  1398  
  1399  func testMTMWinGTLoss(t *testing.T) {
  1400  	// cheat by setting the factor to some specific value, makes it easier to create a scenario where win/loss amounts don't match
  1401  	engine := getTestEngineWithFactor(t, 5)
  1402  	defer engine.Finish()
  1403  
  1404  	price := num.NewUint(100000)
  1405  	one := num.NewUint(1)
  1406  	two := num.Sum(one, one)
  1407  	ctx := context.Background()
  1408  
  1409  	initPos := []testPos{
  1410  		{
  1411  			price: price.Clone(),
  1412  			party: "party-1",
  1413  			size:  10,
  1414  		},
  1415  		{
  1416  			price: price.Clone(),
  1417  			party: "party-2",
  1418  			size:  -4,
  1419  		},
  1420  		{
  1421  			price: price.Clone(),
  1422  			party: "party-3",
  1423  			size:  -1,
  1424  		},
  1425  		{
  1426  			price: price.Clone(),
  1427  			party: "party-4",
  1428  			size:  -5,
  1429  		},
  1430  		{
  1431  			price: price.Clone(),
  1432  			party: "party-6",
  1433  			size:  -1,
  1434  		},
  1435  		{
  1436  			price: price.Clone(),
  1437  			party: "party-7",
  1438  			size:  1,
  1439  		},
  1440  	}
  1441  	init := make([]events.MarketPosition, 0, len(initPos))
  1442  	for _, p := range initPos {
  1443  		init = append(init, p)
  1444  	}
  1445  
  1446  	somePrice := num.Sum(price, two)
  1447  	newPrice := num.Sum(price, one)
  1448  	newParty := testPos{
  1449  		size:  3,
  1450  		price: newPrice.Clone(),
  1451  		party: "party-5",
  1452  	}
  1453  
  1454  	trades := []*types.Trade{
  1455  		{
  1456  			Size:   1,
  1457  			Buyer:  newParty.party,
  1458  			Seller: initPos[0].party,
  1459  			Price:  somePrice.Clone(),
  1460  		},
  1461  		{
  1462  			Size:   1,
  1463  			Buyer:  newParty.party,
  1464  			Seller: initPos[3].party,
  1465  			Price:  somePrice.Clone(),
  1466  		},
  1467  		{
  1468  			Size:   1,
  1469  			Buyer:  newParty.party,
  1470  			Seller: initPos[0].party,
  1471  			Price:  newPrice.Clone(),
  1472  		},
  1473  	}
  1474  	updates := make([]events.MarketPosition, 0, len(initPos)+1)
  1475  	for _, trade := range trades {
  1476  		for i, p := range initPos {
  1477  			if p.party == trade.Seller {
  1478  				p.size -= int64(trade.Size)
  1479  				initPos[i] = p
  1480  			}
  1481  			p.price = trade.Price.Clone()
  1482  		}
  1483  	}
  1484  	for _, p := range initPos {
  1485  		updates = append(updates, p)
  1486  	}
  1487  	updates = append(updates, newParty)
  1488  	engine.Update(init)
  1489  	for _, trade := range trades {
  1490  		engine.AddTrade(trade)
  1491  	}
  1492  	transfers := engine.SettleMTM(ctx, newPrice.Clone(), updates)
  1493  	require.NotEmpty(t, transfers)
  1494  	// just a single transfer of 2
  1495  	require.True(t, transfers[0].Transfer().Amount.Amount.EQ(two))
  1496  }
  1497  
  1498  // {{{.
  1499  func (te *testEngine) getExpiryPositions(positions ...posValue) []events.MarketPosition {
  1500  	te.positions = make([]*mocks.MockMarketPosition, 0, len(positions))
  1501  	mpSlice := make([]events.MarketPosition, 0, len(positions))
  1502  	for _, p := range positions {
  1503  		pos := mocks.NewMockMarketPosition(te.ctrl)
  1504  		// these values should only be obtained once, and assigned internally
  1505  		pos.EXPECT().Party().MinTimes(1).AnyTimes().Return(p.party)
  1506  		pos.EXPECT().Size().MinTimes(1).AnyTimes().Return(p.size)
  1507  		pos.EXPECT().Price().Times(1).Return(p.price)
  1508  		te.positions = append(te.positions, pos)
  1509  		mpSlice = append(mpSlice, pos)
  1510  	}
  1511  	return mpSlice
  1512  }
  1513  
  1514  func (te *testEngine) getMockMarketPositions(data []posValue) ([]settlement.MarketPosition, []events.MarketPosition) {
  1515  	raw, evts := make([]settlement.MarketPosition, 0, len(data)), make([]events.MarketPosition, 0, len(data))
  1516  	for _, pos := range data {
  1517  		mock := mocks.NewMockMarketPosition(te.ctrl)
  1518  		mock.EXPECT().Party().MinTimes(1).Return(pos.party)
  1519  		mock.EXPECT().Size().MinTimes(1).Return(pos.size)
  1520  		mock.EXPECT().Price().MinTimes(1).Return(pos.price)
  1521  		raw = append(raw, mock)
  1522  		evts = append(evts, mock)
  1523  	}
  1524  	return raw, evts
  1525  }
  1526  
  1527  func TestRemoveDistressedNoTrades(t *testing.T) {
  1528  	engine := getTestEngine(t)
  1529  	defer engine.Finish()
  1530  	engine.prod.EXPECT().Settle(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(func(markPrice *num.Uint, settlementData *num.Uint, size num.Decimal) (*types.FinancialAmount, bool, num.Decimal, error) {
  1531  		return &types.FinancialAmount{Amount: num.UintZero()}, false, num.DecimalZero(), nil
  1532  	})
  1533  
  1534  	data := []posValue{
  1535  		{
  1536  			party: "testparty1",
  1537  			price: num.NewUint(1234),
  1538  			size:  100,
  1539  		},
  1540  		{
  1541  			party: "testparty2",
  1542  			price: num.NewUint(1235),
  1543  			size:  0,
  1544  		},
  1545  	}
  1546  	raw, evts := engine.getMockMarketPositions(data)
  1547  	// margin evt
  1548  	marginEvts := make([]events.Margin, 0, len(raw))
  1549  	for _, pe := range raw {
  1550  		marginEvts = append(marginEvts, marginVal{
  1551  			MarketPosition: pe,
  1552  		})
  1553  	}
  1554  
  1555  	assert.False(t, engine.HasTraded())
  1556  	engine.Update(evts)
  1557  	engine.RemoveDistressed(context.Background(), marginEvts)
  1558  	assert.False(t, engine.HasTraded())
  1559  }
  1560  
  1561  func TestConcurrent(t *testing.T) {
  1562  	const N = 10
  1563  
  1564  	engine := getTestEngine(t)
  1565  	defer engine.Finish()
  1566  	engine.prod.EXPECT().Settle(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(func(markPrice *num.Uint, settlementData *num.Uint, size num.Decimal) (*types.FinancialAmount, bool, num.Decimal, error) {
  1567  		return &types.FinancialAmount{Amount: num.UintZero()}, false, num.DecimalZero(), nil
  1568  	})
  1569  
  1570  	cfg := engine.Config
  1571  	cfg.Level.Level = logging.DebugLevel
  1572  	engine.ReloadConf(cfg)
  1573  	cfg.Level.Level = logging.InfoLevel
  1574  	engine.ReloadConf(cfg)
  1575  
  1576  	var wg sync.WaitGroup
  1577  
  1578  	now := time.Now()
  1579  	wg.Add(N * 3)
  1580  	for i := 0; i < N; i++ {
  1581  		data := []posValue{
  1582  			{
  1583  				party: "testparty1",
  1584  				price: num.NewUint(1234),
  1585  				size:  100,
  1586  			},
  1587  			{
  1588  				party: "testparty2",
  1589  				price: num.NewUint(1235),
  1590  				size:  0,
  1591  			},
  1592  		}
  1593  		raw, evts := engine.getMockMarketPositions(data)
  1594  		// margin evt
  1595  		marginEvts := make([]events.Margin, 0, len(raw))
  1596  		for _, pe := range raw {
  1597  			marginEvts = append(marginEvts, marginVal{
  1598  				MarketPosition: pe,
  1599  			})
  1600  		}
  1601  
  1602  		go func() {
  1603  			defer wg.Done()
  1604  			// Update requires posMu
  1605  			engine.Update(evts)
  1606  		}()
  1607  		go func() {
  1608  			defer wg.Done()
  1609  			// RemoveDistressed requires posMu and closedMu
  1610  			engine.RemoveDistressed(context.Background(), marginEvts)
  1611  		}()
  1612  		go func() {
  1613  			defer wg.Done()
  1614  			// Settle requires posMu
  1615  			_, _, err := engine.Settle(now, num.UintZero())
  1616  			assert.NoError(t, err)
  1617  		}()
  1618  	}
  1619  
  1620  	wg.Wait()
  1621  }
  1622  
  1623  // Finish - call finish on controller, remove test state (positions).
  1624  func (te *testEngine) Finish() {
  1625  	te.ctrl.Finish()
  1626  	te.positions = nil
  1627  }
  1628  
  1629  // Quick mock implementation of the events.MarketPosition interface.
  1630  type testPos struct {
  1631  	party                         string
  1632  	size, buy, sell               int64
  1633  	price                         *num.Uint
  1634  	buySumProduct, sellSumProduct uint64
  1635  }
  1636  
  1637  func (t testPos) AverageEntryPrice() *num.Uint {
  1638  	return num.UintZero()
  1639  }
  1640  
  1641  func (t testPos) Party() string {
  1642  	return t.party
  1643  }
  1644  
  1645  func (t testPos) Size() int64 {
  1646  	return t.size
  1647  }
  1648  
  1649  func (t testPos) Buy() int64 {
  1650  	return t.buy
  1651  }
  1652  
  1653  func (t testPos) Sell() int64 {
  1654  	return t.sell
  1655  }
  1656  
  1657  func (t testPos) Price() *num.Uint {
  1658  	if t.price == nil {
  1659  		return num.UintZero()
  1660  	}
  1661  	return t.price
  1662  }
  1663  
  1664  func (t testPos) BuySumProduct() *num.Uint {
  1665  	return num.NewUint(t.buySumProduct)
  1666  }
  1667  
  1668  func (t testPos) SellSumProduct() *num.Uint {
  1669  	return num.NewUint(t.sellSumProduct)
  1670  }
  1671  
  1672  func (t testPos) VWBuy() *num.Uint {
  1673  	if t.buy == 0 {
  1674  		return num.UintZero()
  1675  	}
  1676  	return num.NewUint(t.buySumProduct / uint64(t.buy))
  1677  }
  1678  
  1679  func (t testPos) VWSell() *num.Uint {
  1680  	if t.sell == 0 {
  1681  		return num.UintZero()
  1682  	}
  1683  	return num.NewUint(t.sellSumProduct / uint64(t.sell))
  1684  }
  1685  
  1686  func (t testPos) ClearPotentials() {}
  1687  
  1688  func getTestEngineWithFactor(t *testing.T, f float64) *testEngine {
  1689  	t.Helper()
  1690  	ctrl := gomock.NewController(t)
  1691  	conf := settlement.NewDefaultConfig()
  1692  	prod := mocks.NewMockProduct(ctrl)
  1693  	tsvc := mocks.NewMockTimeService(ctrl)
  1694  	tsvc.EXPECT().GetTimeNow().AnyTimes()
  1695  	broker := bmocks.NewMockBroker(ctrl)
  1696  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
  1697  	market := "BTC/DEC19"
  1698  	prod.EXPECT().GetAsset().AnyTimes().Do(func() string { return "BTC" })
  1699  	return &testEngine{
  1700  		SnapshotEngine: settlement.NewSnapshotEngine(logging.NewTestLogger(), conf, prod, market, tsvc, broker, num.NewDecimalFromFloat(f)),
  1701  		ctrl:           ctrl,
  1702  		prod:           prod,
  1703  		tsvc:           tsvc,
  1704  		broker:         broker,
  1705  		positions:      nil,
  1706  		market:         market,
  1707  	}
  1708  }
  1709  
  1710  func getTestEngine(t *testing.T) *testEngine {
  1711  	t.Helper()
  1712  	return getTestEngineWithFactor(t, 1)
  1713  } // }}}
  1714  
  1715  func (m marginVal) Asset() string {
  1716  	return m.asset
  1717  }
  1718  
  1719  func (m marginVal) MarketID() string {
  1720  	return m.marketID
  1721  }
  1722  
  1723  func (m marginVal) MarginBalance() *num.Uint {
  1724  	return num.NewUint(m.margin)
  1725  }
  1726  
  1727  func (m marginVal) OrderMarginBalance() *num.Uint {
  1728  	return num.NewUint(m.orderMargin)
  1729  }
  1730  
  1731  func (m marginVal) GeneralBalance() *num.Uint {
  1732  	return num.NewUint(m.general)
  1733  }
  1734  
  1735  func (m marginVal) GeneralAccountBalance() *num.Uint {
  1736  	return num.NewUint(m.general)
  1737  }
  1738  
  1739  func (m marginVal) BondBalance() *num.Uint {
  1740  	return num.UintZero()
  1741  }
  1742  
  1743  func (m marginVal) MarginShortFall() *num.Uint {
  1744  	return num.NewUint(m.marginShortFall)
  1745  }
  1746  
  1747  //  vim: set ts=4 sw=4 tw=0 foldlevel=1 foldmethod=marker noet :