code.vegaprotocol.io/vega@v0.79.0/core/risk/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 risk_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"math"
    22  	"sort"
    23  	"testing"
    24  	"time"
    25  
    26  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    27  	"code.vegaprotocol.io/vega/core/config"
    28  	"code.vegaprotocol.io/vega/core/events"
    29  	"code.vegaprotocol.io/vega/core/matching"
    30  	"code.vegaprotocol.io/vega/core/risk"
    31  	"code.vegaprotocol.io/vega/core/risk/mocks"
    32  	"code.vegaprotocol.io/vega/core/types"
    33  	"code.vegaprotocol.io/vega/core/types/statevar"
    34  	"code.vegaprotocol.io/vega/libs/num"
    35  	"code.vegaprotocol.io/vega/logging"
    36  	proto "code.vegaprotocol.io/vega/protos/vega"
    37  
    38  	"github.com/golang/mock/gomock"
    39  	"github.com/stretchr/testify/assert"
    40  	"github.com/stretchr/testify/require"
    41  )
    42  
    43  var DefaultSlippageFactor = num.DecimalFromFloat(0.1)
    44  
    45  func peggedOrderCounterForTest(int64) {}
    46  
    47  type MLEvent interface {
    48  	events.Event
    49  	MarginLevels() proto.MarginLevels
    50  }
    51  
    52  type testEngine struct {
    53  	*risk.Engine
    54  	ctrl      *gomock.Controller
    55  	model     *mocks.MockModel
    56  	orderbook *mocks.MockOrderbook
    57  	tsvc      *mocks.MockTimeService
    58  	broker    *bmocks.MockBroker
    59  	as        *mocks.MockAuctionState
    60  }
    61  
    62  // implements the events.Margin interface.
    63  type testMargin struct {
    64  	party           string
    65  	size            int64
    66  	buy             int64
    67  	sell            int64
    68  	price           uint64
    69  	transfer        *types.Transfer
    70  	asset           string
    71  	margin          uint64
    72  	orderMargin     uint64
    73  	general         uint64
    74  	market          string
    75  	buySumProduct   uint64
    76  	sellSumProduct  uint64
    77  	marginShortFall uint64
    78  }
    79  
    80  var (
    81  	riskFactors = types.RiskFactor{
    82  		Short: num.DecimalFromFloat(.20),
    83  		Long:  num.DecimalFromFloat(.25),
    84  	}
    85  
    86  	markPrice = num.NewUint(100)
    87  )
    88  
    89  func TestUpdateMargins(t *testing.T) {
    90  	t.Run("test time update", testMarginLevelsTS)
    91  	t.Run("Top up margin test", testMarginTopup)
    92  	t.Run("Noop margin test", testMarginNoop)
    93  	t.Run("Margin too high (overflow)", testMarginOverflow)
    94  	t.Run("Margin too high (overflow) - auction ending", testMarginOverflowAuctionEnd)
    95  	t.Run("Update Margin with orders in book", testMarginWithOrderInBook)
    96  	t.Run("Update Margin with orders in book 2", testMarginWithOrderInBook2)
    97  	t.Run("Update Margin with orders in book after parameters update", testMarginWithOrderInBookAfterParamsUpdate)
    98  	t.Run("Top up fail on new order", testMarginTopupOnOrderFailInsufficientFunds)
    99  	t.Run("Margin not released in auction", testMarginNotReleasedInAuction)
   100  	t.Run("Initial margin requirement must be met", testInitialMarginRequirement)
   101  }
   102  
   103  func testMarginLevelsTS(t *testing.T) {
   104  	eng := getTestEngine(t, num.DecimalOne())
   105  
   106  	ctx, cfunc := context.WithCancel(context.Background())
   107  	defer cfunc()
   108  	evt := testMargin{
   109  		party:   "party1",
   110  		size:    1,
   111  		price:   1000,
   112  		asset:   "ETH",
   113  		margin:  10,     // required margin will be > 30 so ensure we don't have enough
   114  		general: 100000, // plenty of balance for the transfer anyway
   115  		market:  "ETH/DEC19",
   116  	}
   117  
   118  	now := time.Now()
   119  	eng.tsvc.EXPECT().GetTimeNow().DoAndReturn(
   120  		func() time.Time {
   121  			return now
   122  		}).Times(1)
   123  
   124  	eng.as.EXPECT().InAuction().AnyTimes().Return(false)
   125  	eng.broker.EXPECT().SendBatch(gomock.Any()).Times(1).Do(func(e []events.Event) {
   126  		mle, ok := e[0].(MLEvent)
   127  		assert.True(t, ok)
   128  		ml := mle.MarginLevels()
   129  		assert.Equal(t, now.UnixNano(), ml.Timestamp)
   130  	})
   131  
   132  	evts := []events.Margin{evt}
   133  	resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil)
   134  	assert.Equal(t, 1, len(resp))
   135  	// ensure we get the correct transfer request back, correct amount etc...
   136  	trans := resp[0].Transfer()
   137  	assert.EqualValues(t, 44, trans.Amount.Amount.Uint64())
   138  	// min = 35 so we go back to maintenance level
   139  	assert.EqualValues(t, 35, trans.MinAmount.Uint64())
   140  	assert.Equal(t, types.TransferTypeMarginLow, trans.Type)
   141  }
   142  
   143  func TestNegativeMargin(t *testing.T) {
   144  	eng := getTestEngine(t, num.DecimalFromInt64(6))
   145  	mtmPrice := num.NewUint(20)
   146  
   147  	ctx, cfunc := context.WithCancel(context.Background())
   148  	defer cfunc()
   149  	evt := testMargin{
   150  		party:   "party1",
   151  		size:    -1,
   152  		price:   10, // holding at 10
   153  		asset:   "ETH",
   154  		margin:  1,                    // required margin will be > 30 so ensure we don't have enough
   155  		general: 10000000000000000000, // plenty of balance for the transfer anyway
   156  		market:  "ETH/DEC19",
   157  		sell:    2, // potential short -1
   158  		buy:     2,
   159  	}
   160  
   161  	now := time.Now()
   162  	eng.tsvc.EXPECT().GetTimeNow().DoAndReturn(
   163  		func() time.Time {
   164  			return now
   165  		}).AnyTimes()
   166  
   167  	eng.as.EXPECT().InAuction().AnyTimes().Return(false)
   168  	eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   169  
   170  	// increment is negative
   171  	inc := num.DecimalFromFloat(-10)
   172  	riskEvts := eng.UpdateMarginsOnSettlement(ctx, []events.Margin{evt}, mtmPrice, inc, nil)
   173  	require.NotEmpty(t, riskEvts)
   174  	initial := riskEvts[0].Transfer().Amount.Amount
   175  	require.Equal(t, "5", initial.String())
   176  }
   177  
   178  func testMarginTopup(t *testing.T) {
   179  	eng := getTestEngine(t, num.DecimalOne())
   180  
   181  	ctx, cfunc := context.WithCancel(context.Background())
   182  	defer cfunc()
   183  	evt := testMargin{
   184  		party:   "party1",
   185  		size:    1,
   186  		price:   1000,
   187  		asset:   "ETH",
   188  		margin:  10,     // required margin will be > 30 so ensure we don't have enough
   189  		general: 100000, // plenty of balance for the transfer anyway
   190  		market:  "ETH/DEC19",
   191  	}
   192  	eng.tsvc.EXPECT().GetTimeNow().Times(1)
   193  	eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   194  	eng.as.EXPECT().InAuction().AnyTimes().Return(false)
   195  	evts := []events.Margin{evt}
   196  	resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil)
   197  	assert.Equal(t, 1, len(resp))
   198  	// ensure we get the correct transfer request back, correct amount etc...
   199  	trans := resp[0].Transfer()
   200  	assert.EqualValues(t, 44, trans.Amount.Amount.Uint64())
   201  	// min = 35 so we go back to maintenance level
   202  	assert.EqualValues(t, 35, trans.MinAmount.Uint64())
   203  	assert.Equal(t, types.TransferTypeMarginLow, trans.Type)
   204  }
   205  
   206  func TestMarginTopupPerpetual(t *testing.T) {
   207  	eng := getTestEngine(t, num.DecimalOne())
   208  
   209  	ctx, cfunc := context.WithCancel(context.Background())
   210  	defer cfunc()
   211  	evt := testMargin{
   212  		party:   "party1",
   213  		size:    1,
   214  		price:   1000,
   215  		asset:   "ETH",
   216  		margin:  10,     // required margin will be > 30 so ensure we don't have enough
   217  		general: 100000, // plenty of balance for the transfer anyway
   218  		market:  "ETH/DEC19",
   219  	}
   220  
   221  	// lets pretend the perpetual margin factor is 0.5 and the funding payement was 10, 5
   222  	inc := num.DecimalFromFloat(0.5).Mul(num.DecimalFromInt64(10))
   223  
   224  	eng.tsvc.EXPECT().GetTimeNow().Times(2)
   225  	eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   226  	eng.as.EXPECT().InAuction().AnyTimes().Return(false)
   227  	evts := []events.Margin{evt}
   228  	resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, inc, nil)
   229  	assert.Equal(t, 1, len(resp))
   230  
   231  	mm := resp[0].MarginLevels().MaintenanceMargin
   232  	assert.Equal(t, "50", mm.String())
   233  
   234  	// now do it again with the funding payment negated, the margin should be as if we were not a perp
   235  	// and 5 less
   236  	resp = eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, inc.Neg(), nil)
   237  	assert.Equal(t, 1, len(resp))
   238  
   239  	mm = resp[0].MarginLevels().MaintenanceMargin
   240  	assert.Equal(t, "45", mm.String())
   241  }
   242  
   243  func testMarginNotReleasedInAuction(t *testing.T) {
   244  	eng := getTestEngine(t, num.DecimalOne())
   245  
   246  	ctx, cfunc := context.WithCancel(context.Background())
   247  	defer cfunc()
   248  	evt := testMargin{
   249  		party:   "party1",
   250  		size:    1,
   251  		price:   1000,
   252  		asset:   "ETH",
   253  		margin:  70, // relese level is 35 so we need more than that
   254  		general: 100000,
   255  		market:  "ETH/DEC19",
   256  	}
   257  	eng.tsvc.EXPECT().GetTimeNow().Times(1)
   258  	eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   259  	eng.as.EXPECT().InAuction().AnyTimes().Return(true)
   260  	eng.as.EXPECT().CanLeave().AnyTimes().Return(false)
   261  	evts := []events.Margin{evt}
   262  	resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil)
   263  	assert.Equal(t, 0, len(resp))
   264  }
   265  
   266  func testMarginTopupOnOrderFailInsufficientFunds(t *testing.T) {
   267  	eng := getTestEngine(t, num.DecimalOne())
   268  
   269  	_, cfunc := context.WithCancel(context.Background())
   270  	defer cfunc()
   271  	evt := testMargin{
   272  		party:   "party1",
   273  		size:    1,
   274  		price:   1000,
   275  		asset:   "ETH",
   276  		margin:  10, // maring and general combined are not enough to get a sufficient margin
   277  		general: 10,
   278  		market:  "ETH/DEC19",
   279  	}
   280  	eng.tsvc.EXPECT().GetTimeNow().Times(1)
   281  	eng.as.EXPECT().InAuction().AnyTimes().Return(false)
   282  	riskevt, _, err := eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil)
   283  	assert.Nil(t, riskevt)
   284  	assert.NotNil(t, err)
   285  	assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error())
   286  }
   287  
   288  func testMarginNoop(t *testing.T) {
   289  	eng := getTestEngine(t, num.DecimalOne())
   290  
   291  	eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   292  	ctx, cfunc := context.WithCancel(context.Background())
   293  	defer cfunc()
   294  	evt := testMargin{
   295  		party:   "party1",
   296  		size:    1,
   297  		price:   1000,
   298  		asset:   "ETH",
   299  		margin:  30,     // more than enough margin to cover the position, not enough to trigger transfer to general
   300  		general: 100000, // plenty of balance for the transfer anyway
   301  		market:  "ETH/DEC19",
   302  	}
   303  	eng.tsvc.EXPECT().GetTimeNow().Times(1)
   304  	eng.as.EXPECT().InAuction().AnyTimes().Return(false)
   305  
   306  	evts := []events.Margin{evt}
   307  	resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil)
   308  	assert.Equal(t, 1, len(resp))
   309  }
   310  
   311  func testMarginOverflow(t *testing.T) {
   312  	eng := getTestEngine(t, num.DecimalOne())
   313  
   314  	eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   315  	ctx, cfunc := context.WithCancel(context.Background())
   316  	defer cfunc()
   317  	evt := testMargin{
   318  		party:   "party1",
   319  		size:    1,
   320  		price:   1000,
   321  		asset:   "ETH",
   322  		margin:  500,    // required margin will be > 35 (release), so ensure we don't have enough
   323  		general: 100000, // plenty of balance for the transfer anyway
   324  		market:  "ETH/DEC19",
   325  	}
   326  	eng.tsvc.EXPECT().GetTimeNow().Times(1)
   327  	eng.as.EXPECT().InAuction().Times(2).Return(false)
   328  	evts := []events.Margin{evt}
   329  	resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), nil)
   330  	assert.Equal(t, 1, len(resp))
   331  
   332  	// ensure we get the correct transfer request back, correct amount etc...
   333  	trans := resp[0].Transfer()
   334  	assert.EqualValues(t, 446, trans.Amount.Amount.Uint64())
   335  	assert.Equal(t, types.TransferTypeMarginHigh, trans.Type)
   336  }
   337  
   338  func testMarginOverflowAuctionEnd(t *testing.T) {
   339  	eng := getTestEngine(t, num.DecimalOne())
   340  
   341  	eng.broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   342  	ctx, cfunc := context.WithCancel(context.Background())
   343  	defer cfunc()
   344  	evt := testMargin{
   345  		party:   "party1",
   346  		size:    1,
   347  		price:   1000,
   348  		asset:   "ETH",
   349  		margin:  500,    // required margin will be > 35 (release), so ensure we don't have enough
   350  		general: 100000, // plenty of balance for the transfer anyway
   351  		market:  "ETH/DEC19",
   352  	}
   353  	// we're still in auction...
   354  	eng.tsvc.EXPECT().GetTimeNow().Times(1)
   355  	eng.as.EXPECT().InAuction().Times(2).Return(true)
   356  	// but the auction is ending
   357  	eng.as.EXPECT().CanLeave().Times(2).Return(true)
   358  	// eng.as.EXPECT().InAuction().AnyTimes().Return(false)
   359  	evts := []events.Margin{evt}
   360  	resp := eng.UpdateMarginsOnSettlement(ctx, evts, markPrice, num.DecimalZero(), markPrice)
   361  	assert.Equal(t, 1, len(resp))
   362  
   363  	// ensure we get the correct transfer request back, correct amount etc...
   364  	trans := resp[0].Transfer()
   365  	assert.EqualValues(t, 446, trans.Amount.Amount.Uint64())
   366  	assert.Equal(t, types.TransferTypeMarginHigh, trans.Type)
   367  }
   368  
   369  func TestMarginWithNoOrdersOnBook(t *testing.T) {
   370  	// assure state-aware and static methods provide results consistent with each other
   371  	r := &types.RiskFactor{
   372  		Short: num.DecimalFromFloat(.11),
   373  		Long:  num.DecimalFromFloat(.10),
   374  	}
   375  	mc := &types.MarginCalculator{
   376  		ScalingFactors: &types.ScalingFactors{
   377  			SearchLevel:       num.DecimalFromFloat(1.1),
   378  			InitialMargin:     num.DecimalFromFloat(1.2),
   379  			CollateralRelease: num.DecimalFromFloat(1.3),
   380  		},
   381  	}
   382  	markPrice := int64(144)
   383  
   384  	marketID := "testingmarket"
   385  
   386  	conf := config.NewDefaultConfig()
   387  	log := logging.NewTestLogger()
   388  	ctrl := gomock.NewController(t)
   389  	model := mocks.NewMockModel(ctrl)
   390  	ts := mocks.NewMockTimeService(ctrl)
   391  	broker := bmocks.NewMockBroker(ctrl)
   392  
   393  	ts.EXPECT().GetTimeNow().AnyTimes()
   394  	broker.EXPECT().Send(gomock.Any()).AnyTimes()
   395  
   396  	model.EXPECT().DefaultRiskFactors().Return(r).AnyTimes()
   397  
   398  	statevar := mocks.NewMockStateVarEngine(ctrl)
   399  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   400  	statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   401  	book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest)
   402  
   403  	testCases := []struct {
   404  		expectedMargin          string
   405  		positionSize            int64
   406  		buyOrders               []*risk.OrderInfo
   407  		sellOrders              []*risk.OrderInfo
   408  		linearSlippageFactor    num.Decimal
   409  		quadraticSlippageFactor num.Decimal
   410  		margin_funding_factor   float64
   411  		funding_payment_to_date float64
   412  		auction                 bool
   413  		auctionPrice            num.Decimal
   414  	}{
   415  		{
   416  			expectedMargin:          "87",
   417  			positionSize:            6,
   418  			buyOrders:               nil,
   419  			sellOrders:              nil,
   420  			linearSlippageFactor:    num.DecimalZero(),
   421  			quadraticSlippageFactor: num.DecimalZero(),
   422  			margin_funding_factor:   0,
   423  			funding_payment_to_date: 0,
   424  			auction:                 false,
   425  		},
   426  		{
   427  			expectedMargin:          "96",
   428  			positionSize:            -6,
   429  			buyOrders:               nil,
   430  			sellOrders:              nil,
   431  			linearSlippageFactor:    num.DecimalZero(),
   432  			quadraticSlippageFactor: num.DecimalZero(),
   433  			margin_funding_factor:   0,
   434  			funding_payment_to_date: 0,
   435  			auction:                 true,
   436  		},
   437  		{
   438  			expectedMargin: "335",
   439  			positionSize:   9,
   440  			buyOrders: []*risk.OrderInfo{
   441  				{
   442  					TrueRemaining: 3,
   443  					Price:         num.DecimalFromInt64(markPrice - 3),
   444  					IsMarketOrder: false,
   445  				},
   446  				{
   447  					TrueRemaining: 5,
   448  					Price:         num.DecimalFromInt64(markPrice - 12),
   449  					IsMarketOrder: false,
   450  				},
   451  			},
   452  			sellOrders: []*risk.OrderInfo{
   453  				{
   454  					TrueRemaining: 5,
   455  					Price:         num.DecimalFromInt64(markPrice + 2),
   456  					IsMarketOrder: false,
   457  				},
   458  				{
   459  					TrueRemaining: 2,
   460  					Price:         num.DecimalFromInt64(markPrice + 7),
   461  					IsMarketOrder: false,
   462  				},
   463  			},
   464  			linearSlippageFactor:    num.DecimalZero(),
   465  			quadraticSlippageFactor: num.DecimalZero(),
   466  			margin_funding_factor:   1,
   467  			funding_payment_to_date: 10,
   468  			auction:                 false,
   469  		},
   470  		{
   471  			expectedMargin: "328",
   472  			positionSize:   9,
   473  			buyOrders: []*risk.OrderInfo{
   474  				{
   475  					TrueRemaining: 3,
   476  					Price:         num.DecimalFromInt64(markPrice - 3),
   477  					IsMarketOrder: false,
   478  				},
   479  				{
   480  					TrueRemaining: 5,
   481  					Price:         num.DecimalFromInt64(markPrice - 12),
   482  					IsMarketOrder: false,
   483  				},
   484  			},
   485  			sellOrders: []*risk.OrderInfo{
   486  				{
   487  					TrueRemaining: 5,
   488  					Price:         num.DecimalFromInt64(markPrice + 2),
   489  					IsMarketOrder: false,
   490  				},
   491  				{
   492  					TrueRemaining: 2,
   493  					Price:         num.DecimalFromInt64(markPrice + 7),
   494  					IsMarketOrder: false,
   495  				},
   496  			},
   497  			linearSlippageFactor:    num.DecimalZero(),
   498  			quadraticSlippageFactor: num.DecimalZero(),
   499  			margin_funding_factor:   1,
   500  			funding_payment_to_date: 10,
   501  			auction:                 true,
   502  		},
   503  		{
   504  			expectedMargin: "232",
   505  			positionSize:   -7,
   506  			buyOrders: []*risk.OrderInfo{
   507  				{
   508  					TrueRemaining: 3,
   509  					Price:         num.DecimalFromInt64(markPrice - 3),
   510  					IsMarketOrder: false,
   511  				},
   512  				{
   513  					TrueRemaining: 5,
   514  					Price:         num.DecimalFromInt64(markPrice - 12),
   515  					IsMarketOrder: false,
   516  				},
   517  			},
   518  			sellOrders: []*risk.OrderInfo{
   519  				{
   520  					TrueRemaining: 5,
   521  					Price:         num.DecimalFromInt64(markPrice + 2),
   522  					IsMarketOrder: false,
   523  				},
   524  				{
   525  					TrueRemaining: 2,
   526  					Price:         num.DecimalFromInt64(markPrice + 7),
   527  					IsMarketOrder: false,
   528  				},
   529  			},
   530  			linearSlippageFactor:    num.DecimalFromFloat(0.01),
   531  			quadraticSlippageFactor: num.DecimalZero(),
   532  			margin_funding_factor:   1,
   533  			funding_payment_to_date: 10,
   534  			auction:                 false,
   535  		},
   536  		{
   537  			expectedMargin: "236",
   538  			positionSize:   -7,
   539  			buyOrders: []*risk.OrderInfo{
   540  				{
   541  					TrueRemaining: 3,
   542  					Price:         num.DecimalFromInt64(markPrice - 3),
   543  					IsMarketOrder: false,
   544  				},
   545  				{
   546  					TrueRemaining: 5,
   547  					Price:         num.DecimalFromInt64(markPrice - 12),
   548  					IsMarketOrder: false,
   549  				},
   550  			},
   551  			sellOrders: []*risk.OrderInfo{
   552  				{
   553  					TrueRemaining: 5,
   554  					Price:         num.DecimalFromInt64(markPrice + 2),
   555  					IsMarketOrder: false,
   556  				},
   557  				{
   558  					TrueRemaining: 2,
   559  					Price:         num.DecimalFromInt64(markPrice + 7),
   560  					IsMarketOrder: false,
   561  				},
   562  			},
   563  			linearSlippageFactor:    num.DecimalFromFloat(0.01),
   564  			quadraticSlippageFactor: num.DecimalFromFloat(0.0001),
   565  			margin_funding_factor:   1,
   566  			funding_payment_to_date: 10,
   567  			auction:                 true,
   568  		},
   569  		{
   570  			expectedMargin: "121",
   571  			positionSize:   1,
   572  			buyOrders:      []*risk.OrderInfo{},
   573  			sellOrders: []*risk.OrderInfo{
   574  				{
   575  					TrueRemaining: 5,
   576  					Price:         num.DecimalFromInt64(markPrice + 2),
   577  					IsMarketOrder: false,
   578  				},
   579  				{
   580  					TrueRemaining: 2,
   581  					Price:         num.DecimalFromInt64(markPrice + 7),
   582  					IsMarketOrder: false,
   583  				},
   584  			},
   585  			linearSlippageFactor:    num.DecimalFromFloat(0.01),
   586  			quadraticSlippageFactor: num.DecimalZero(),
   587  			margin_funding_factor:   1,
   588  			funding_payment_to_date: 10,
   589  			auction:                 false,
   590  		},
   591  		{
   592  			expectedMargin: "1754",
   593  			positionSize:   1,
   594  			buyOrders: []*risk.OrderInfo{
   595  				{
   596  					TrueRemaining: 100,
   597  					Price:         num.DecimalFromInt64(1),
   598  					IsMarketOrder: false,
   599  				},
   600  				{
   601  					TrueRemaining: 20,
   602  					Price:         num.DecimalFromInt64(2),
   603  					IsMarketOrder: false,
   604  				},
   605  			},
   606  			sellOrders:              []*risk.OrderInfo{},
   607  			linearSlippageFactor:    num.DecimalFromFloat(0.01),
   608  			quadraticSlippageFactor: num.DecimalZero(),
   609  			margin_funding_factor:   1,
   610  			funding_payment_to_date: 10,
   611  			auction:                 false,
   612  			auctionPrice:            num.DecimalFromInt64(123),
   613  		},
   614  	}
   615  
   616  	for _, tc := range testCases {
   617  		buy := int64(0)
   618  		buySumProduct := uint64(0)
   619  		for _, o := range tc.buyOrders {
   620  			buy += int64(o.TrueRemaining)
   621  			buySumProduct += o.TrueRemaining * o.Price.BigInt().Uint64()
   622  		}
   623  		sell := int64(0)
   624  		sellSumProduct := uint64(0)
   625  		for _, o := range tc.sellOrders {
   626  			sell += int64(o.TrueRemaining)
   627  			sellSumProduct += o.TrueRemaining * o.Price.BigInt().Uint64()
   628  		}
   629  
   630  		evt := testMargin{
   631  			party:          "tx",
   632  			size:           tc.positionSize,
   633  			buy:            buy,
   634  			sell:           sell,
   635  			buySumProduct:  buySumProduct,
   636  			sellSumProduct: sellSumProduct,
   637  			price:          uint64(markPrice),
   638  			asset:          "ETH",
   639  			margin:         0,
   640  			general:        100000,
   641  			market:         marketID,
   642  		}
   643  
   644  		constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date)
   645  		as := mocks.NewMockAuctionState(ctrl)
   646  		as.EXPECT().InAuction().AnyTimes().Return(tc.auction).AnyTimes()
   647  		as.EXPECT().CanLeave().AnyTimes().Return(!tc.auction).AnyTimes()
   648  		testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, marketID, "ETH", statevar, num.DecimalFromInt64(1), false, nil, tc.linearSlippageFactor, tc.quadraticSlippageFactor)
   649  
   650  		apUint, overflow := num.UintFromDecimal(tc.auctionPrice)
   651  		require.False(t, overflow)
   652  
   653  		riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, num.UintFromUint64(uint64(markPrice)), constantPerUnitPositionSize, apUint)
   654  		require.NoError(t, err)
   655  		require.NotNil(t, riskevt)
   656  		margins := riskevt.MarginLevels()
   657  		require.Equal(t, tc.expectedMargin, margins.MaintenanceMargin.String())
   658  
   659  		marginRecalcualted := risk.CalculateMaintenanceMarginWithSlippageFactors(evt.size, tc.buyOrders, tc.sellOrders, num.DecimalFromInt64(markPrice), num.DecimalOne(), tc.linearSlippageFactor, tc.quadraticSlippageFactor, r.Long, r.Short, constantPerUnitPositionSize, tc.auction, tc.auctionPrice)
   660  		require.Equal(t, margins.MaintenanceMargin.Float64(), marginRecalcualted.RoundUp(0).InexactFloat64())
   661  	}
   662  }
   663  
   664  func testMarginWithOrderInBook(t *testing.T) {
   665  	// custom risk factors
   666  	r := &types.RiskFactor{
   667  		Short: num.DecimalFromFloat(.11),
   668  		Long:  num.DecimalFromFloat(.10),
   669  	}
   670  	// custom scaling factor
   671  	mc := &types.MarginCalculator{
   672  		ScalingFactors: &types.ScalingFactors{
   673  			SearchLevel:       num.DecimalFromFloat(1.1),
   674  			InitialMargin:     num.DecimalFromFloat(1.2),
   675  			CollateralRelease: num.DecimalFromFloat(1.3),
   676  		},
   677  	}
   678  
   679  	markPrice := num.NewUint(144)
   680  
   681  	// list of order in the book before the test happen
   682  	ordersInBook := []struct {
   683  		volume int64
   684  		price  *num.Uint
   685  		tid    string
   686  		side   types.Side
   687  	}{
   688  		// asks
   689  		{volume: 3, price: num.NewUint(258), tid: "t1", side: types.SideSell},
   690  		{volume: 5, price: num.NewUint(240), tid: "t2", side: types.SideSell},
   691  		{volume: 3, price: num.NewUint(188), tid: "t3", side: types.SideSell},
   692  
   693  		// bids
   694  		{volume: 1, price: num.NewUint(120), tid: "t4", side: types.SideBuy},
   695  		{volume: 4, price: num.NewUint(110), tid: "t5", side: types.SideBuy},
   696  		{volume: 5, price: num.NewUint(108), tid: "t6", side: types.SideBuy},
   697  	}
   698  
   699  	marketID := "testingmarket"
   700  
   701  	conf := config.NewDefaultConfig()
   702  	log := logging.NewTestLogger()
   703  	ctrl := gomock.NewController(t)
   704  	model := mocks.NewMockModel(ctrl)
   705  	ts := mocks.NewMockTimeService(ctrl)
   706  	broker := bmocks.NewMockBroker(ctrl)
   707  	as := mocks.NewMockAuctionState(ctrl)
   708  	ts.EXPECT().GetTimeNow().Times(1)
   709  	broker.EXPECT().Send(gomock.Any()).AnyTimes()
   710  
   711  	// instantiate the book then fill it with the orders
   712  
   713  	book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest)
   714  
   715  	for _, v := range ordersInBook {
   716  		o := &types.Order{
   717  			ID:          fmt.Sprintf("o-%v-%v", v.tid, marketID),
   718  			MarketID:    marketID,
   719  			Party:       "A",
   720  			Side:        v.side,
   721  			Price:       v.price.Clone(),
   722  			Size:        uint64(v.volume),
   723  			Remaining:   uint64(v.volume),
   724  			TimeInForce: types.OrderTimeInForceGTT,
   725  			Type:        types.OrderTypeLimit,
   726  			Status:      types.OrderStatusActive,
   727  			ExpiresAt:   10000,
   728  		}
   729  		_, err := book.SubmitOrder(o)
   730  		assert.Nil(t, err)
   731  	}
   732  
   733  	model.EXPECT().DefaultRiskFactors().Return(r).Times(1)
   734  	as.EXPECT().InAuction().AnyTimes().Return(false)
   735  	statevar := mocks.NewMockStateVarEngine(ctrl)
   736  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   737  	statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any())
   738  	testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, "mktid", "ETH", statevar, num.DecimalFromInt64(1), false, nil, DefaultSlippageFactor, DefaultSlippageFactor)
   739  	evt := testMargin{
   740  		party:   "tx",
   741  		size:    10,
   742  		buy:     4,
   743  		sell:    8,
   744  		price:   144,
   745  		asset:   "ETH",
   746  		margin:  500,
   747  		general: 100000,
   748  		market:  "ETH/DEC19",
   749  	}
   750  	// insufficient orders on the book
   751  	riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, markPrice.Clone(), num.DecimalZero(), nil)
   752  	assert.NotNil(t, riskevt)
   753  	if riskevt == nil {
   754  		t.Fatal("expecting non nil risk update")
   755  	}
   756  
   757  	// maintenance margin = (10*0.1+100*0.1)*144+144*0.1*14=1785.6=1786
   758  	assert.Nil(t, err)
   759  	margins := riskevt.MarginLevels()
   760  	searchLevel, _ := mc.ScalingFactors.SearchLevel.Float64()
   761  	initialMargin, _ := mc.ScalingFactors.InitialMargin.Float64()
   762  	colRelease, _ := mc.ScalingFactors.CollateralRelease.Float64()
   763  	assert.EqualValues(t, 1786, margins.MaintenanceMargin.Uint64())
   764  	assert.Equal(t, uint64(1786*searchLevel), margins.SearchLevel.Uint64())
   765  	assert.Equal(t, uint64(1786*initialMargin), margins.InitialMargin.Uint64())
   766  	assert.Equal(t, uint64(1786*colRelease), margins.CollateralReleaseLevel.Uint64())
   767  }
   768  
   769  // testcase 1 from: https://drive.google.com/file/d/1B8-rLK2NB6rWvjzZX9sLtqOQzLz8s2ky/view
   770  func testMarginWithOrderInBook2(t *testing.T) {
   771  	// custom risk factors
   772  	r := &types.RiskFactor{
   773  		Short: num.DecimalFromFloat(.2),
   774  		Long:  num.DecimalFromFloat(.1),
   775  	}
   776  	_ = r
   777  	// custom scaling factor
   778  	mc := &types.MarginCalculator{
   779  		ScalingFactors: &types.ScalingFactors{
   780  			SearchLevel:       num.DecimalFromFloat(3.2),
   781  			InitialMargin:     num.DecimalFromFloat(4),
   782  			CollateralRelease: num.DecimalFromFloat(5),
   783  		},
   784  	}
   785  
   786  	// list of order in the book before the test happen
   787  	ordersInBook := []struct {
   788  		volume int64
   789  		price  *num.Uint
   790  		tid    string
   791  		side   types.Side
   792  	}{
   793  		// asks
   794  		{volume: 100, price: num.NewUint(250), tid: "t1", side: types.SideSell},
   795  		{volume: 11, price: num.NewUint(140), tid: "t2", side: types.SideSell},
   796  		{volume: 2, price: num.NewUint(112), tid: "t3", side: types.SideSell},
   797  		// bids
   798  		{volume: 1, price: num.NewUint(100), tid: "t4", side: types.SideBuy},
   799  		{volume: 3, price: num.NewUint(96), tid: "t5", side: types.SideBuy},
   800  		{volume: 15, price: num.NewUint(90), tid: "t6", side: types.SideBuy},
   801  		{volume: 50, price: num.NewUint(87), tid: "t7", side: types.SideBuy},
   802  	}
   803  
   804  	marketID := "testingmarket"
   805  
   806  	conf := config.NewDefaultConfig()
   807  	log := logging.NewTestLogger()
   808  	ctrl := gomock.NewController(t)
   809  	model := mocks.NewMockModel(ctrl)
   810  	ts := mocks.NewMockTimeService(ctrl)
   811  	broker := bmocks.NewMockBroker(ctrl)
   812  	as := mocks.NewMockAuctionState(ctrl)
   813  	ts.EXPECT().GetTimeNow().Times(1)
   814  	broker.EXPECT().Send(gomock.Any()).AnyTimes()
   815  
   816  	model.EXPECT().DefaultRiskFactors().Return(r).Times(1)
   817  
   818  	as.EXPECT().InAuction().AnyTimes().Return(false)
   819  	// instantiate the book then fill it with the orders
   820  
   821  	book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest)
   822  
   823  	for _, v := range ordersInBook {
   824  		o := &types.Order{
   825  			ID:          fmt.Sprintf("o-%v-%v", v.tid, marketID),
   826  			MarketID:    marketID,
   827  			Party:       "A",
   828  			Side:        v.side,
   829  			Price:       v.price.Clone(),
   830  			Size:        uint64(v.volume),
   831  			Remaining:   uint64(v.volume),
   832  			TimeInForce: types.OrderTimeInForceGTT,
   833  			Type:        types.OrderTypeLimit,
   834  			Status:      types.OrderStatusActive,
   835  			ExpiresAt:   10000,
   836  		}
   837  		_, err := book.SubmitOrder(o)
   838  		assert.Nil(t, err)
   839  	}
   840  
   841  	statevar := mocks.NewMockStateVarEngine(ctrl)
   842  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   843  	statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any())
   844  	testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, "mktid", "ETH", statevar, num.DecimalFromInt64(1), false, nil, DefaultSlippageFactor, DefaultSlippageFactor)
   845  	evt := testMargin{
   846  		party:   "tx",
   847  		size:    13,
   848  		buy:     0,
   849  		sell:    0,
   850  		price:   150,
   851  		asset:   "ETH",
   852  		margin:  0,
   853  		general: 100000,
   854  		market:  "ETH/DEC19",
   855  	}
   856  
   857  	previousMarkPrice := num.NewUint(103)
   858  
   859  	riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, previousMarkPrice, num.DecimalZero(), nil)
   860  	assert.NotNil(t, riskevt)
   861  	if riskevt == nil {
   862  		t.Fatal("expecting non nil risk update")
   863  	}
   864  	assert.Nil(t, err)
   865  	margins := riskevt.MarginLevels()
   866  	searchLevel, _ := mc.ScalingFactors.SearchLevel.Float64()
   867  	initialMargin, _ := mc.ScalingFactors.InitialMargin.Float64()
   868  	colRelease, _ := mc.ScalingFactors.CollateralRelease.Float64()
   869  
   870  	assert.Equal(t, uint64(2009), margins.MaintenanceMargin.Uint64())
   871  	assert.Equal(t, uint64(2009*searchLevel), margins.SearchLevel.Uint64())
   872  	assert.Equal(t, uint64(2009*initialMargin), margins.InitialMargin.Uint64())
   873  	assert.Equal(t, uint64(2009*colRelease), margins.CollateralReleaseLevel.Uint64())
   874  }
   875  
   876  func testMarginWithOrderInBookAfterParamsUpdate(t *testing.T) {
   877  	// custom risk factors
   878  	r := &types.RiskFactor{
   879  		Short: num.DecimalFromFloat(.11),
   880  		Long:  num.DecimalFromFloat(.10),
   881  	}
   882  	// custom scaling factor
   883  	mc := &types.MarginCalculator{
   884  		ScalingFactors: &types.ScalingFactors{
   885  			SearchLevel:       num.DecimalFromFloat(1.1),
   886  			InitialMargin:     num.DecimalFromFloat(1.2),
   887  			CollateralRelease: num.DecimalFromFloat(1.3),
   888  		},
   889  	}
   890  
   891  	markPrice := num.NewUint(144)
   892  
   893  	// list of order in the book before the test happen
   894  	ordersInBook := []struct {
   895  		volume int64
   896  		price  *num.Uint
   897  		tid    string
   898  		side   types.Side
   899  	}{
   900  		// asks
   901  		{volume: 3, price: num.NewUint(258), tid: "t1", side: types.SideSell},
   902  		{volume: 5, price: num.NewUint(240), tid: "t2", side: types.SideSell},
   903  		{volume: 3, price: num.NewUint(188), tid: "t3", side: types.SideSell},
   904  
   905  		// bids
   906  		{volume: 1, price: num.NewUint(120), tid: "t4", side: types.SideBuy},
   907  		{volume: 4, price: num.NewUint(110), tid: "t5", side: types.SideBuy},
   908  		{volume: 5, price: num.NewUint(108), tid: "t6", side: types.SideBuy},
   909  	}
   910  
   911  	marketID := "testingmarket"
   912  
   913  	conf := config.NewDefaultConfig()
   914  	log := logging.NewTestLogger()
   915  	ctrl := gomock.NewController(t)
   916  	model := mocks.NewMockModel(ctrl)
   917  	ts := mocks.NewMockTimeService(ctrl)
   918  	broker := bmocks.NewMockBroker(ctrl)
   919  	as := mocks.NewMockAuctionState(ctrl)
   920  	ts.EXPECT().GetTimeNow().Times(2)
   921  	broker.EXPECT().Send(gomock.Any()).AnyTimes()
   922  
   923  	// instantiate the book then fill it with the orders
   924  
   925  	book := matching.NewOrderBook(log, conf.Execution.Matching, marketID, false, peggedOrderCounterForTest)
   926  
   927  	for _, v := range ordersInBook {
   928  		o := &types.Order{
   929  			ID:          fmt.Sprintf("o-%v-%v", v.tid, marketID),
   930  			MarketID:    marketID,
   931  			Party:       "A",
   932  			Side:        v.side,
   933  			Price:       v.price.Clone(),
   934  			Size:        uint64(v.volume),
   935  			Remaining:   uint64(v.volume),
   936  			TimeInForce: types.OrderTimeInForceGTT,
   937  			Type:        types.OrderTypeLimit,
   938  			Status:      types.OrderStatusActive,
   939  			ExpiresAt:   10000,
   940  		}
   941  		_, err := book.SubmitOrder(o)
   942  		assert.Nil(t, err)
   943  	}
   944  
   945  	model.EXPECT().DefaultRiskFactors().Return(r).Times(1)
   946  	as.EXPECT().InAuction().AnyTimes().Return(false)
   947  	statevarEngine := mocks.NewMockStateVarEngine(ctrl)
   948  	statevarEngine.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   949  	statevarEngine.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any())
   950  	asset := "ETH"
   951  	testE := risk.NewEngine(log, conf.Execution.Risk, mc, model, book, as, ts, broker, marketID, asset, statevarEngine, num.DecimalFromInt64(1), false, nil, DefaultSlippageFactor, DefaultSlippageFactor)
   952  
   953  	evt := testMargin{
   954  		party:   "tx",
   955  		size:    10,
   956  		buy:     4,
   957  		sell:    8,
   958  		price:   144,
   959  		asset:   asset,
   960  		margin:  500,
   961  		general: 100000,
   962  		market:  marketID,
   963  	}
   964  	riskevt, _, err := testE.UpdateMarginOnNewOrder(context.Background(), evt, markPrice.Clone(), num.DecimalZero(), nil)
   965  	require.NotNil(t, riskevt)
   966  	require.Nil(t, err)
   967  
   968  	margins := riskevt.MarginLevels()
   969  	searchLevel, _ := mc.ScalingFactors.SearchLevel.Float64()
   970  	initialMargin, _ := mc.ScalingFactors.InitialMargin.Float64()
   971  	colRelease, _ := mc.ScalingFactors.CollateralRelease.Float64()
   972  	assert.EqualValues(t, 1786, margins.MaintenanceMargin.Uint64())
   973  	assert.Equal(t, uint64(1786*searchLevel), margins.SearchLevel.Uint64())
   974  	assert.Equal(t, uint64(1786*initialMargin), margins.InitialMargin.Uint64())
   975  	assert.Equal(t, uint64(1786*colRelease), margins.CollateralReleaseLevel.Uint64())
   976  
   977  	updatedRF := &types.RiskFactor{
   978  		Short: num.DecimalFromFloat(.12),
   979  		Long:  num.DecimalFromFloat(.11),
   980  	}
   981  	updatedMC := &types.MarginCalculator{
   982  		ScalingFactors: &types.ScalingFactors{
   983  			SearchLevel:       num.DecimalFromFloat(1.2),
   984  			InitialMargin:     num.DecimalFromFloat(1.4),
   985  			CollateralRelease: num.DecimalFromFloat(1.4),
   986  		},
   987  	}
   988  
   989  	// updating the slippage should change the margin too
   990  	updatedSlippage := num.DecimalFromFloat(0.1).Mul(DefaultSlippageFactor)
   991  	model.EXPECT().DefaultRiskFactors().Return(updatedRF).Times(1)
   992  	statevarEngine.EXPECT().NewEvent(asset, marketID, statevar.EventTypeMarketUpdated)
   993  	testE.UpdateModel(statevarEngine, updatedMC, model, updatedSlippage, updatedSlippage)
   994  
   995  	evt = testMargin{
   996  		party:   "tx",
   997  		size:    10,
   998  		buy:     4,
   999  		sell:    8,
  1000  		price:   144,
  1001  		asset:   asset,
  1002  		margin:  500,
  1003  		general: 100000,
  1004  		market:  marketID,
  1005  	}
  1006  	riskevt, _, err = testE.UpdateMarginOnNewOrder(context.Background(), evt, markPrice.Clone(), num.DecimalZero(), nil)
  1007  	require.NotNil(t, riskevt)
  1008  	require.Nil(t, err)
  1009  
  1010  	margins = riskevt.MarginLevels()
  1011  	searchLevel, _ = updatedMC.ScalingFactors.SearchLevel.Float64()
  1012  	initialMargin, _ = updatedMC.ScalingFactors.InitialMargin.Float64()
  1013  	colRelease, _ = updatedMC.ScalingFactors.CollateralRelease.Float64()
  1014  	assert.EqualValues(t, 381, margins.MaintenanceMargin.Uint64())
  1015  	assert.Equal(t, uint64(381*searchLevel), margins.SearchLevel.Uint64())
  1016  	assert.Equal(t, uint64(381*initialMargin), margins.InitialMargin.Uint64())
  1017  	assert.Equal(t, uint64(381*colRelease), margins.CollateralReleaseLevel.Uint64())
  1018  }
  1019  
  1020  func testInitialMarginRequirement(t *testing.T) {
  1021  	eng := getTestEngine(t, num.DecimalOne())
  1022  
  1023  	_, cfunc := context.WithCancel(context.Background())
  1024  	defer cfunc()
  1025  
  1026  	initialMargin := uint64(336)
  1027  
  1028  	evt := testMargin{
  1029  		party:   "party1",
  1030  		size:    -4,
  1031  		price:   1000,
  1032  		asset:   "ETH",
  1033  		margin:  0,
  1034  		general: initialMargin - 1,
  1035  		market:  "ETH/DEC19",
  1036  	}
  1037  	eng.tsvc.EXPECT().GetTimeNow().Times(6)
  1038  	eng.as.EXPECT().InAuction().Times(2).Return(false)
  1039  	eng.broker.EXPECT().SendBatch(gomock.Any()).Times(3)
  1040  	riskevt, _, err := eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil)
  1041  	assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error())
  1042  	assert.Nil(t, riskevt)
  1043  
  1044  	evt.general = initialMargin
  1045  	riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil)
  1046  	assert.NoError(t, err)
  1047  	assert.NotNil(t, riskevt)
  1048  	assert.True(t, riskevt.MarginLevels().InitialMargin.EQ(num.NewUint(initialMargin)))
  1049  
  1050  	eng.as.EXPECT().InAuction().Times(4).Return(true)
  1051  	eng.as.EXPECT().CanLeave().Times(4).Return(false)
  1052  
  1053  	slippageFactor := DefaultSlippageFactor.InexactFloat64()
  1054  	size := math.Abs(float64(evt.size))
  1055  	rf := eng.GetRiskFactors()
  1056  	initialMarginScalingFactor := 1.2
  1057  	initialMarginAuction := math.Ceil(initialMarginScalingFactor * (size*slippageFactor + size*size*slippageFactor + size*rf.Short.InexactFloat64()) * markPrice.ToDecimal().InexactFloat64())
  1058  
  1059  	evt.general = uint64(initialMarginAuction) - 1
  1060  	riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil)
  1061  	assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error())
  1062  	assert.Nil(t, riskevt)
  1063  
  1064  	evt.general = uint64(initialMarginAuction)
  1065  	riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil)
  1066  	assert.NoError(t, err)
  1067  	assert.NotNil(t, riskevt)
  1068  	assert.True(t, riskevt.MarginLevels().InitialMargin.EQ(num.NewUint(uint64(initialMarginAuction))))
  1069  
  1070  	evt.sell = 7
  1071  	evt.sellSumProduct = 123
  1072  
  1073  	ordersBit := evt.SellSumProduct().Float64() * rf.Short.InexactFloat64()
  1074  	initialMarginAuction = math.Ceil(initialMarginAuction + initialMarginScalingFactor*ordersBit)
  1075  
  1076  	evt.general = uint64(initialMarginAuction) - 1
  1077  	riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil)
  1078  	assert.Error(t, err, risk.ErrInsufficientFundsForInitialMargin.Error())
  1079  	assert.Nil(t, riskevt)
  1080  
  1081  	evt.general = uint64(math.Ceil(initialMarginAuction))
  1082  	riskevt, _, err = eng.UpdateMarginOnNewOrder(context.Background(), evt, markPrice, num.DecimalZero(), nil)
  1083  	assert.NoError(t, err)
  1084  	assert.NotNil(t, riskevt)
  1085  	assert.True(t, riskevt.MarginLevels().InitialMargin.EQ(num.NewUint(uint64(initialMarginAuction))))
  1086  }
  1087  
  1088  func TestMaintenanceMarign(t *testing.T) {
  1089  	relativeTolerance := num.DecimalFromFloat(0.000001)
  1090  
  1091  	testCases := []struct {
  1092  		markPrice               float64
  1093  		positionFactor          float64
  1094  		positionSize            int64
  1095  		buyOrders               []*risk.OrderInfo
  1096  		sellOrders              []*risk.OrderInfo
  1097  		linearSlippageFactor    float64
  1098  		quadraticSlippageFactor float64
  1099  		riskFactorLong          float64
  1100  		riskFactorShort         float64
  1101  		margin_funding_factor   float64
  1102  		funding_payment_to_date float64
  1103  		auction                 bool
  1104  	}{
  1105  		{
  1106  			markPrice:      123.4,
  1107  			positionFactor: 1,
  1108  			positionSize:   40000,
  1109  			buyOrders: []*risk.OrderInfo{
  1110  				{1, num.NewDecimalFromFloat(0), false},
  1111  			},
  1112  			sellOrders: []*risk.OrderInfo{
  1113  				{1, num.NewDecimalFromFloat(0), false},
  1114  			},
  1115  			linearSlippageFactor:    0,
  1116  			quadraticSlippageFactor: 0,
  1117  			riskFactorLong:          0.1,
  1118  			riskFactorShort:         0.1,
  1119  			auction:                 false,
  1120  		},
  1121  		{
  1122  			markPrice:      123.4,
  1123  			positionFactor: 10,
  1124  			positionSize:   40000,
  1125  			buyOrders: []*risk.OrderInfo{
  1126  				{1, num.NewDecimalFromFloat(111.1), false},
  1127  			},
  1128  			sellOrders: []*risk.OrderInfo{
  1129  				{1, num.NewDecimalFromFloat(133.3), false},
  1130  			},
  1131  			linearSlippageFactor:    0,
  1132  			quadraticSlippageFactor: 0,
  1133  			riskFactorLong:          0.1,
  1134  			riskFactorShort:         0.1,
  1135  			auction:                 true,
  1136  		},
  1137  		{
  1138  			markPrice:      123.4,
  1139  			positionFactor: 10,
  1140  			positionSize:   0,
  1141  			buyOrders: []*risk.OrderInfo{
  1142  				{1, num.NewDecimalFromFloat(111.1), false},
  1143  			},
  1144  			sellOrders: []*risk.OrderInfo{
  1145  				{1, num.NewDecimalFromFloat(133.3), false},
  1146  			},
  1147  			linearSlippageFactor:    0,
  1148  			quadraticSlippageFactor: 0,
  1149  			riskFactorLong:          0.1,
  1150  			riskFactorShort:         0.1,
  1151  			auction:                 true,
  1152  		},
  1153  		{
  1154  			markPrice:      123.4,
  1155  			positionFactor: 10,
  1156  			positionSize:   0,
  1157  			buyOrders: []*risk.OrderInfo{
  1158  				{10000, num.NewDecimalFromFloat(111.1), false},
  1159  			},
  1160  			sellOrders: []*risk.OrderInfo{
  1161  				{30000, num.NewDecimalFromFloat(133.3), false},
  1162  			},
  1163  			linearSlippageFactor:    0,
  1164  			quadraticSlippageFactor: 0,
  1165  			riskFactorLong:          0.1,
  1166  			riskFactorShort:         0.2,
  1167  			auction:                 false,
  1168  		},
  1169  		{
  1170  			markPrice:      123.4,
  1171  			positionFactor: 100,
  1172  			positionSize:   0,
  1173  			buyOrders: []*risk.OrderInfo{
  1174  				{40000, num.NewDecimalFromFloat(111.1), false},
  1175  			},
  1176  			sellOrders: []*risk.OrderInfo{
  1177  				{30000, num.NewDecimalFromFloat(133.3), false},
  1178  			},
  1179  			linearSlippageFactor:    0.5,
  1180  			quadraticSlippageFactor: 0.1,
  1181  			riskFactorLong:          0.1,
  1182  			riskFactorShort:         0.2,
  1183  			auction:                 false,
  1184  		},
  1185  		{
  1186  			markPrice:      123.4,
  1187  			positionFactor: 100,
  1188  			positionSize:   0,
  1189  			buyOrders: []*risk.OrderInfo{
  1190  				{10000, num.NewDecimalFromFloat(111.4), false},
  1191  				{30000, num.NewDecimalFromFloat(111), false},
  1192  			},
  1193  			sellOrders: []*risk.OrderInfo{
  1194  				{10000, num.NewDecimalFromFloat(133.9), false},
  1195  				{20000, num.NewDecimalFromFloat(133.0), false},
  1196  			},
  1197  			linearSlippageFactor:    0.5,
  1198  			quadraticSlippageFactor: 0.1,
  1199  			riskFactorLong:          0.1,
  1200  			riskFactorShort:         0.2,
  1201  			auction:                 false,
  1202  		},
  1203  		{
  1204  			markPrice:      123.4,
  1205  			positionFactor: 100,
  1206  			positionSize:   0,
  1207  			buyOrders: []*risk.OrderInfo{
  1208  				{10000, num.NewDecimalFromFloat(111.4), false},
  1209  				{30000, num.NewDecimalFromFloat(111), false},
  1210  				{20000, num.NewDecimalFromFloat(0), true},
  1211  			},
  1212  			sellOrders: []*risk.OrderInfo{
  1213  				{10000, num.NewDecimalFromFloat(133.9), false},
  1214  				{20000, num.NewDecimalFromFloat(133.0), false},
  1215  				{30000, num.NewDecimalFromFloat(0), true},
  1216  			},
  1217  			linearSlippageFactor:    0.5,
  1218  			quadraticSlippageFactor: 0.1,
  1219  			riskFactorLong:          0.1,
  1220  			riskFactorShort:         0.2,
  1221  			auction:                 false,
  1222  		},
  1223  		{
  1224  			markPrice:      123.4,
  1225  			positionFactor: 100,
  1226  			positionSize:   0,
  1227  			buyOrders: []*risk.OrderInfo{
  1228  				{10000, num.NewDecimalFromFloat(111.4), false},
  1229  				{30000, num.NewDecimalFromFloat(111), false},
  1230  				{20000, num.NewDecimalFromFloat(0), true},
  1231  			},
  1232  			sellOrders: []*risk.OrderInfo{
  1233  				{10000, num.NewDecimalFromFloat(133.9), false},
  1234  				{20000, num.NewDecimalFromFloat(133.0), false},
  1235  				{30000, num.NewDecimalFromFloat(0), true},
  1236  			},
  1237  			linearSlippageFactor:    0.5,
  1238  			quadraticSlippageFactor: 0.1,
  1239  			riskFactorLong:          0.1,
  1240  			riskFactorShort:         0.2,
  1241  			auction:                 false,
  1242  			margin_funding_factor:   0.5,
  1243  			funding_payment_to_date: 75,
  1244  		},
  1245  		{
  1246  			markPrice:      123.4,
  1247  			positionFactor: 100,
  1248  			positionSize:   0,
  1249  			buyOrders: []*risk.OrderInfo{
  1250  				{10000, num.NewDecimalFromFloat(111.4), false},
  1251  				{30000, num.NewDecimalFromFloat(111), false},
  1252  				{20000, num.NewDecimalFromFloat(0), true},
  1253  			},
  1254  			sellOrders: []*risk.OrderInfo{
  1255  				{10000, num.NewDecimalFromFloat(133.9), false},
  1256  				{20000, num.NewDecimalFromFloat(133.0), false},
  1257  				{30000, num.NewDecimalFromFloat(0), true},
  1258  			},
  1259  			linearSlippageFactor:    0.5,
  1260  			quadraticSlippageFactor: 0.1,
  1261  			riskFactorLong:          0.1,
  1262  			riskFactorShort:         0.2,
  1263  			auction:                 false,
  1264  			margin_funding_factor:   1,
  1265  			funding_payment_to_date: 300,
  1266  		},
  1267  	}
  1268  
  1269  	for i, tc := range testCases {
  1270  		markPrice := num.DecimalFromFloat(tc.markPrice)
  1271  		positionFactor := num.DecimalFromFloat(tc.positionFactor)
  1272  		buySumProduct := num.DecimalZero()
  1273  		sellSumProduct := num.DecimalZero()
  1274  		buySize := int64(0)
  1275  		sellSize := int64(0)
  1276  
  1277  		linearSlippageFactor := num.DecimalFromFloat(tc.linearSlippageFactor)
  1278  		quadraticSlippageFactor := num.DecimalFromFloat(tc.quadraticSlippageFactor)
  1279  		riskFactorLong := num.DecimalFromFloat(tc.riskFactorLong)
  1280  		riskFactorShort := num.DecimalFromFloat(tc.riskFactorShort)
  1281  
  1282  		constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date)
  1283  
  1284  		positionSize := tc.positionSize
  1285  		for _, o := range tc.buyOrders {
  1286  			s := int64(o.TrueRemaining)
  1287  			if o.IsMarketOrder {
  1288  				positionSize += s
  1289  			} else {
  1290  				buySize += s
  1291  				buySumProduct = buySumProduct.Add(num.DecimalFromInt64(s).Mul(o.Price))
  1292  			}
  1293  		}
  1294  
  1295  		for _, o := range tc.sellOrders {
  1296  			s := int64(o.TrueRemaining)
  1297  			if o.IsMarketOrder {
  1298  				positionSize -= s
  1299  			} else {
  1300  				sellSize += s
  1301  				sellSumProduct = sellSumProduct.Add(num.DecimalFromInt64(s).Mul(o.Price))
  1302  			}
  1303  		}
  1304  
  1305  		openVolume := num.DecimalFromInt64(positionSize).Div(positionFactor)
  1306  		openVolumeAbs := openVolume.Abs()
  1307  		expectedMarginShort, expectedMarginLong := num.DecimalZero(), num.DecimalZero()
  1308  		slippage := markPrice.Mul(openVolumeAbs.Mul(linearSlippageFactor).Add(openVolumeAbs.Mul(openVolumeAbs).Mul(quadraticSlippageFactor)))
  1309  
  1310  		if positionSize-sellSize < 0 {
  1311  			expectedMarginShort = slippage.Add(openVolumeAbs.Mul(markPrice).Mul(riskFactorShort))
  1312  			orders := num.DecimalFromInt64(sellSize).Div(positionFactor).Abs().Mul(riskFactorShort)
  1313  			if tc.auction {
  1314  				expectedMarginShort = expectedMarginShort.Add(orders.Mul(sellSumProduct))
  1315  			} else {
  1316  				expectedMarginShort = expectedMarginShort.Add(orders.Mul(markPrice))
  1317  			}
  1318  		}
  1319  		if positionSize+buySize > 0 {
  1320  			expectedMarginLong = slippage.Add(openVolumeAbs.Mul(markPrice).Mul(riskFactorLong))
  1321  			orders := num.DecimalFromInt64(buySize).Div(positionFactor).Abs().Mul(riskFactorLong)
  1322  			if tc.auction {
  1323  				expectedMarginLong = expectedMarginLong.Add(orders.Mul(buySumProduct))
  1324  			} else {
  1325  				expectedMarginLong = expectedMarginLong.Add(orders.Mul(markPrice))
  1326  			}
  1327  		}
  1328  		expectedMargin := num.MaxD(expectedMarginShort, expectedMarginLong).Add(num.MaxD(num.DecimalZero(), openVolume.Mul(constantPerUnitPositionSize)))
  1329  
  1330  		actualMargin := risk.CalculateMaintenanceMarginWithSlippageFactors(tc.positionSize, tc.buyOrders, tc.sellOrders, markPrice, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, tc.auction, num.DecimalZero())
  1331  
  1332  		require.True(t, expectedMargin.Div(actualMargin).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: expectedMargin=%s, actualMargin:=%s", i+1, expectedMargin, actualMargin))
  1333  	}
  1334  }
  1335  
  1336  func TestLiquidationPriceWithNoOrders(t *testing.T) {
  1337  	relativeTolerance := num.DecimalFromFloat(0.000001)
  1338  
  1339  	testCases := []struct {
  1340  		markPrice               float64
  1341  		positionFactor          float64
  1342  		positionSize            int64
  1343  		linearSlippageFactor    float64
  1344  		quadraticSlippageFactor float64
  1345  		riskFactorLong          float64
  1346  		riskFactorShort         float64
  1347  		collateralFactor        float64
  1348  		margin_funding_factor   float64
  1349  		funding_payment_to_date float64
  1350  		expectError             bool
  1351  	}{
  1352  		{
  1353  			markPrice:               123.4,
  1354  			positionFactor:          1,
  1355  			positionSize:            40000,
  1356  			linearSlippageFactor:    0,
  1357  			quadraticSlippageFactor: 0,
  1358  			riskFactorLong:          0.1,
  1359  			riskFactorShort:         0.11,
  1360  			collateralFactor:        1.7,
  1361  		},
  1362  		{
  1363  			markPrice:               1234.5,
  1364  			positionFactor:          10,
  1365  			positionSize:            40000,
  1366  			linearSlippageFactor:    0.5,
  1367  			quadraticSlippageFactor: 0,
  1368  			riskFactorLong:          0.1,
  1369  			riskFactorShort:         0.11,
  1370  			collateralFactor:        1.1,
  1371  		},
  1372  		{
  1373  			markPrice:               1234.5,
  1374  			positionFactor:          100,
  1375  			positionSize:            -40000,
  1376  			linearSlippageFactor:    0.5,
  1377  			quadraticSlippageFactor: 0.01,
  1378  			riskFactorLong:          0.1,
  1379  			riskFactorShort:         0.11,
  1380  			collateralFactor:        3,
  1381  		},
  1382  		{
  1383  			markPrice:               1234.5,
  1384  			positionFactor:          1000,
  1385  			positionSize:            -40000,
  1386  			linearSlippageFactor:    0.5,
  1387  			quadraticSlippageFactor: 0.1,
  1388  			riskFactorLong:          0.1,
  1389  			riskFactorShort:         0.11,
  1390  			collateralFactor:        0.2,
  1391  		},
  1392  		{
  1393  			markPrice:               1,
  1394  			positionFactor:          1,
  1395  			positionSize:            1,
  1396  			linearSlippageFactor:    0,
  1397  			quadraticSlippageFactor: 0,
  1398  			riskFactorLong:          1,
  1399  			riskFactorShort:         1,
  1400  			collateralFactor:        2.5,
  1401  			expectError:             true,
  1402  		},
  1403  		{
  1404  			markPrice:               110,
  1405  			positionFactor:          1,
  1406  			positionSize:            41,
  1407  			linearSlippageFactor:    0.5,
  1408  			quadraticSlippageFactor: 0.01,
  1409  			riskFactorLong:          0.1,
  1410  			riskFactorShort:         0.11,
  1411  			collateralFactor:        1000,
  1412  		},
  1413  		{
  1414  			markPrice:               110,
  1415  			positionFactor:          1,
  1416  			positionSize:            41,
  1417  			linearSlippageFactor:    0.05,
  1418  			quadraticSlippageFactor: 0,
  1419  			riskFactorLong:          0.1,
  1420  			riskFactorShort:         0.11,
  1421  			collateralFactor:        3,
  1422  			margin_funding_factor:   0.5,
  1423  			funding_payment_to_date: 50,
  1424  		},
  1425  		{
  1426  			markPrice:               110,
  1427  			positionFactor:          1,
  1428  			positionSize:            -41,
  1429  			linearSlippageFactor:    0.05,
  1430  			quadraticSlippageFactor: 0,
  1431  			riskFactorLong:          0.1,
  1432  			riskFactorShort:         0.11,
  1433  			collateralFactor:        3,
  1434  			margin_funding_factor:   1,
  1435  			funding_payment_to_date: -300,
  1436  		},
  1437  		{
  1438  			markPrice:               110,
  1439  			positionFactor:          1,
  1440  			positionSize:            -41,
  1441  			linearSlippageFactor:    0.05,
  1442  			quadraticSlippageFactor: 0,
  1443  			riskFactorLong:          0.1,
  1444  			riskFactorShort:         0.11,
  1445  			collateralFactor:        3,
  1446  			margin_funding_factor:   1,
  1447  			funding_payment_to_date: 300,
  1448  		},
  1449  	}
  1450  
  1451  	for i, tc := range testCases {
  1452  		markPrice := num.DecimalFromFloat(tc.markPrice)
  1453  		positionFactor := num.DecimalFromFloat(tc.positionFactor)
  1454  
  1455  		linearSlippageFactor := num.DecimalFromFloat(tc.linearSlippageFactor)
  1456  		quadraticSlippageFactor := num.DecimalFromFloat(tc.quadraticSlippageFactor)
  1457  		riskFactorLong := num.DecimalFromFloat(tc.riskFactorLong)
  1458  		riskFactorShort := num.DecimalFromFloat(tc.riskFactorShort)
  1459  		constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date)
  1460  		maintenanceMargin := risk.CalculateMaintenanceMarginWithSlippageFactors(tc.positionSize, nil, nil, markPrice, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalZero())
  1461  
  1462  		maintenanceMarginFp := maintenanceMargin.InexactFloat64()
  1463  		require.Greater(t, maintenanceMarginFp, 0.0)
  1464  
  1465  		liquidationPrice, _, _, err := risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, nil, nil, markPrice, maintenanceMargin, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalZero())
  1466  		if tc.expectError {
  1467  			require.Error(t, err)
  1468  			continue
  1469  		}
  1470  		require.NoError(t, err)
  1471  
  1472  		liquidationPriceFp := liquidationPrice.InexactFloat64()
  1473  		require.GreaterOrEqual(t, liquidationPriceFp, 0.0)
  1474  
  1475  		require.True(t, markPrice.Div(liquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v:", i+1))
  1476  
  1477  		collateral := maintenanceMargin.Mul(num.DecimalFromFloat(tc.collateralFactor))
  1478  
  1479  		liquidationPrice, _, _, err = risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, nil, nil, markPrice, collateral, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne())
  1480  		require.NoError(t, err)
  1481  		require.False(t, liquidationPrice.IsNegative())
  1482  
  1483  		liquidationPriceFp = liquidationPrice.InexactFloat64()
  1484  		require.GreaterOrEqual(t, liquidationPriceFp, 0.0)
  1485  
  1486  		marginAtLiquidationPrice := risk.CalculateMaintenanceMarginWithSlippageFactors(tc.positionSize, nil, nil, liquidationPrice, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalZero())
  1487  		openVolume := num.DecimalFromInt64(tc.positionSize).Div(positionFactor)
  1488  		mtmLoss := liquidationPrice.Sub(markPrice).Mul(openVolume)
  1489  		collateralAfterLoss := collateral.Add(mtmLoss)
  1490  
  1491  		if !marginAtLiquidationPrice.IsZero() {
  1492  			require.True(t, collateralAfterLoss.Div(marginAtLiquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: collateralAfterLoss=%s, marginAtLiquidationPrice:=%s", i+1, collateralAfterLoss, marginAtLiquidationPrice))
  1493  		} else {
  1494  			require.True(t, liquidationPrice.IsZero(), fmt.Sprintf("Test case %v:", i+1))
  1495  			require.True(t, collateralAfterLoss.IsNegative(), fmt.Sprintf("Test case %v:", i+1))
  1496  		}
  1497  
  1498  		liquidationPriceIsolatedMode, _, _, err := risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, nil, nil, markPrice, collateral, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, true, num.DecimalOne())
  1499  		require.NoError(t, err)
  1500  		require.Equal(t, liquidationPrice, liquidationPrice, liquidationPriceIsolatedMode)
  1501  	}
  1502  }
  1503  
  1504  func TestLiquidationPriceWithOrders(t *testing.T) {
  1505  	relativeTolerance := num.DecimalFromFloat(0.01)
  1506  	testCases := []struct {
  1507  		markPrice               float64
  1508  		positionFactor          float64
  1509  		positionSize            int64
  1510  		buyOrders               []*risk.OrderInfo
  1511  		sellOrders              []*risk.OrderInfo
  1512  		linearSlippageFactor    float64
  1513  		quadraticSlippageFactor float64
  1514  		riskFactorLong          float64
  1515  		riskFactorShort         float64
  1516  		collateralAvailable     float64
  1517  		margin_funding_factor   float64
  1518  		funding_payment_to_date float64
  1519  	}{
  1520  		{
  1521  			markPrice:      123.4,
  1522  			positionFactor: 1,
  1523  			positionSize:   0,
  1524  			buyOrders: []*risk.OrderInfo{
  1525  				{1, num.NewDecimalFromFloat(110), false},
  1526  			},
  1527  			sellOrders: []*risk.OrderInfo{
  1528  				{1, num.NewDecimalFromFloat(130), false},
  1529  			},
  1530  			linearSlippageFactor:    0,
  1531  			quadraticSlippageFactor: 0,
  1532  			riskFactorLong:          0.1,
  1533  			riskFactorShort:         0.11,
  1534  			collateralAvailable:     100,
  1535  		},
  1536  		{
  1537  			markPrice:      123.4,
  1538  			positionFactor: 1,
  1539  			positionSize:   0,
  1540  			buyOrders: []*risk.OrderInfo{
  1541  				{39, num.NewDecimalFromFloat(110), false},
  1542  			},
  1543  			sellOrders: []*risk.OrderInfo{
  1544  				{40, num.NewDecimalFromFloat(130), false},
  1545  			},
  1546  			linearSlippageFactor:    0.5,
  1547  			quadraticSlippageFactor: 0.01,
  1548  			riskFactorLong:          0.1,
  1549  			riskFactorShort:         0.11,
  1550  			collateralAvailable:     50800,
  1551  		},
  1552  		{
  1553  			markPrice:      123.4,
  1554  			positionFactor: 1,
  1555  			positionSize:   20,
  1556  			buyOrders: []*risk.OrderInfo{
  1557  				{39, num.NewDecimalFromFloat(110), false},
  1558  			},
  1559  			sellOrders: []*risk.OrderInfo{
  1560  				{40, num.NewDecimalFromFloat(130), false},
  1561  			},
  1562  			linearSlippageFactor:    0.01,
  1563  			quadraticSlippageFactor: 0.000001,
  1564  			riskFactorLong:          0.1,
  1565  			riskFactorShort:         0.11,
  1566  			collateralAvailable:     50800,
  1567  		},
  1568  		{
  1569  			markPrice:      123.4,
  1570  			positionFactor: 100,
  1571  			positionSize:   -2000,
  1572  			buyOrders: []*risk.OrderInfo{
  1573  				{3900, num.NewDecimalFromFloat(110), false},
  1574  			},
  1575  			sellOrders: []*risk.OrderInfo{
  1576  				{4000, num.NewDecimalFromFloat(130), false},
  1577  			},
  1578  			linearSlippageFactor:    0.01,
  1579  			quadraticSlippageFactor: 0.000001,
  1580  			riskFactorLong:          0.1,
  1581  			riskFactorShort:         0.11,
  1582  			collateralAvailable:     50800,
  1583  		},
  1584  		{
  1585  			markPrice:      123.4,
  1586  			positionFactor: 0.1,
  1587  			positionSize:   -2,
  1588  			buyOrders: []*risk.OrderInfo{
  1589  				{3, num.NewDecimalFromFloat(110), false},
  1590  			},
  1591  			sellOrders: []*risk.OrderInfo{
  1592  				{4, num.NewDecimalFromFloat(130), false},
  1593  			},
  1594  			linearSlippageFactor:    0.01,
  1595  			quadraticSlippageFactor: 0.000001,
  1596  			riskFactorLong:          0.1,
  1597  			riskFactorShort:         0.11,
  1598  			collateralAvailable:     50800,
  1599  		},
  1600  		{
  1601  			markPrice:      101.2,
  1602  			positionFactor: 100,
  1603  			positionSize:   -2000,
  1604  			buyOrders: []*risk.OrderInfo{
  1605  				{3900, num.NewDecimalFromFloat(110), false},
  1606  			},
  1607  			sellOrders: []*risk.OrderInfo{
  1608  				{4000, num.NewDecimalFromFloat(130), false},
  1609  			},
  1610  			linearSlippageFactor:    0.01,
  1611  			quadraticSlippageFactor: 0.000001,
  1612  			riskFactorLong:          0.1,
  1613  			riskFactorShort:         0.11,
  1614  			collateralAvailable:     50800,
  1615  		},
  1616  		{
  1617  			markPrice:      144.5,
  1618  			positionFactor: 100,
  1619  			positionSize:   -2000,
  1620  			buyOrders: []*risk.OrderInfo{
  1621  				{3900, num.NewDecimalFromFloat(110), false},
  1622  			},
  1623  			sellOrders: []*risk.OrderInfo{
  1624  				{4000, num.NewDecimalFromFloat(130), false},
  1625  			},
  1626  			linearSlippageFactor:    0.01,
  1627  			quadraticSlippageFactor: 0.000001,
  1628  			riskFactorLong:          0.1,
  1629  			riskFactorShort:         0.11,
  1630  			collateralAvailable:     50800,
  1631  		},
  1632  		{
  1633  			markPrice:      123.4,
  1634  			positionFactor: 100,
  1635  			positionSize:   -2000,
  1636  			buyOrders: []*risk.OrderInfo{
  1637  				{1800, num.NewDecimalFromFloat(100), false},
  1638  				{1700, num.NewDecimalFromFloat(110), false},
  1639  			},
  1640  			sellOrders: []*risk.OrderInfo{
  1641  				{3000, num.NewDecimalFromFloat(120), false},
  1642  				{2000, num.NewDecimalFromFloat(130), false},
  1643  				{1000, num.NewDecimalFromFloat(140), false},
  1644  			},
  1645  			linearSlippageFactor:    0.01,
  1646  			quadraticSlippageFactor: 0.000001,
  1647  			riskFactorLong:          0.1,
  1648  			riskFactorShort:         0.11,
  1649  			collateralAvailable:     50800,
  1650  		},
  1651  		{
  1652  			markPrice:      123.4,
  1653  			positionFactor: 100,
  1654  			positionSize:   -2000,
  1655  			buyOrders: []*risk.OrderInfo{
  1656  				{1800, num.NewDecimalFromFloat(100), false},
  1657  				{1700, num.NewDecimalFromFloat(0), true},
  1658  			},
  1659  			sellOrders: []*risk.OrderInfo{
  1660  				{3000, num.NewDecimalFromFloat(120), false},
  1661  				{2000, num.NewDecimalFromFloat(130), false},
  1662  				{1000, num.NewDecimalFromFloat(0), true},
  1663  			},
  1664  			linearSlippageFactor:    0.01,
  1665  			quadraticSlippageFactor: 0.000001,
  1666  			riskFactorLong:          0.1,
  1667  			riskFactorShort:         0.11,
  1668  			collateralAvailable:     50800,
  1669  		},
  1670  		{
  1671  			markPrice:      123.4,
  1672  			positionFactor: 1,
  1673  			positionSize:   1,
  1674  			buyOrders:      []*risk.OrderInfo{},
  1675  			sellOrders: []*risk.OrderInfo{
  1676  				{2, num.NewDecimalFromFloat(0), true},
  1677  				{99, num.NewDecimalFromFloat(0), true},
  1678  			},
  1679  			linearSlippageFactor:    0.01,
  1680  			quadraticSlippageFactor: 0.000001,
  1681  			riskFactorLong:          0.1,
  1682  			riskFactorShort:         0.11,
  1683  			collateralAvailable:     2345,
  1684  		},
  1685  		{
  1686  			markPrice:      123.4,
  1687  			positionFactor: 1,
  1688  			positionSize:   1,
  1689  			buyOrders:      []*risk.OrderInfo{},
  1690  			sellOrders: []*risk.OrderInfo{
  1691  				{1, num.NewDecimalFromFloat(0), true},
  1692  				{100, num.NewDecimalFromFloat(0), true},
  1693  			},
  1694  			linearSlippageFactor:    0.01,
  1695  			quadraticSlippageFactor: 0.000001,
  1696  			riskFactorLong:          0.1,
  1697  			riskFactorShort:         0.11,
  1698  			collateralAvailable:     2345,
  1699  		},
  1700  		{
  1701  			markPrice:      123.4,
  1702  			positionFactor: 100,
  1703  			positionSize:   -2000,
  1704  			buyOrders: []*risk.OrderInfo{
  1705  				{1800, num.NewDecimalFromFloat(100), false},
  1706  				{1700, num.NewDecimalFromFloat(0), true},
  1707  			},
  1708  			sellOrders: []*risk.OrderInfo{
  1709  				{3000, num.NewDecimalFromFloat(120), false},
  1710  				{2000, num.NewDecimalFromFloat(130), false},
  1711  				{1000, num.NewDecimalFromFloat(0), true},
  1712  			},
  1713  			linearSlippageFactor:    0.01,
  1714  			quadraticSlippageFactor: 0.000001,
  1715  			riskFactorLong:          0.1,
  1716  			riskFactorShort:         0.11,
  1717  			collateralAvailable:     50800,
  1718  			margin_funding_factor:   0.5,
  1719  			funding_payment_to_date: 50,
  1720  		},
  1721  		{
  1722  			markPrice:      123.4,
  1723  			positionFactor: 100,
  1724  			positionSize:   -2000,
  1725  			buyOrders: []*risk.OrderInfo{
  1726  				{1800, num.NewDecimalFromFloat(100), false},
  1727  				{1700, num.NewDecimalFromFloat(0), true},
  1728  			},
  1729  			sellOrders: []*risk.OrderInfo{
  1730  				{3000, num.NewDecimalFromFloat(120), false},
  1731  				{2000, num.NewDecimalFromFloat(130), false},
  1732  				{1000, num.NewDecimalFromFloat(0), true},
  1733  			},
  1734  			linearSlippageFactor:    0.01,
  1735  			quadraticSlippageFactor: 0.000001,
  1736  			riskFactorLong:          0.1,
  1737  			riskFactorShort:         0.11,
  1738  			collateralAvailable:     50800,
  1739  			margin_funding_factor:   0.5,
  1740  			funding_payment_to_date: -50,
  1741  		},
  1742  		{
  1743  			markPrice:      123.4,
  1744  			positionFactor: 1,
  1745  			positionSize:   20,
  1746  			buyOrders: []*risk.OrderInfo{
  1747  				{39, num.NewDecimalFromFloat(110), false},
  1748  			},
  1749  			sellOrders: []*risk.OrderInfo{
  1750  				{40, num.NewDecimalFromFloat(130), false},
  1751  			},
  1752  			linearSlippageFactor:    0.01,
  1753  			quadraticSlippageFactor: 0.000001,
  1754  			riskFactorLong:          0.1,
  1755  			riskFactorShort:         0.11,
  1756  			collateralAvailable:     50800,
  1757  			margin_funding_factor:   1,
  1758  			funding_payment_to_date: 300,
  1759  		},
  1760  	}
  1761  
  1762  	for i, tc := range testCases {
  1763  		markPrice := num.DecimalFromFloat(tc.markPrice)
  1764  		positionFactor := num.DecimalFromFloat(tc.positionFactor)
  1765  		collateral := num.DecimalFromFloat(tc.collateralAvailable)
  1766  
  1767  		linearSlippageFactor := num.DecimalFromFloat(tc.linearSlippageFactor)
  1768  		quadraticSlippageFactor := num.DecimalFromFloat(tc.quadraticSlippageFactor)
  1769  		riskFactorLong := num.DecimalFromFloat(tc.riskFactorLong)
  1770  		riskFactorShort := num.DecimalFromFloat(tc.riskFactorShort)
  1771  
  1772  		constantPerUnitPositionSize := num.DecimalFromFloat(tc.margin_funding_factor * tc.funding_payment_to_date)
  1773  		positionOnly, withBuy, withSell, err := risk.CalculateLiquidationPriceWithSlippageFactors(tc.positionSize, tc.buyOrders, tc.sellOrders, markPrice, collateral, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne())
  1774  		require.NoError(t, err, fmt.Sprintf("Test case %v:", i+1))
  1775  
  1776  		sPositionOnly := positionOnly.String()
  1777  		sWithBuy := withBuy.String()
  1778  		sWithSell := withSell.String()
  1779  
  1780  		t.Logf("positionOnly=%s, withBuy=%s, withSell=%s", sPositionOnly, sWithBuy, sWithSell)
  1781  
  1782  		if tc.positionSize == 0 {
  1783  			require.True(t, positionOnly.IsZero(), fmt.Sprintf("Test case %v:", i+1))
  1784  		}
  1785  		if tc.positionSize > 0 {
  1786  			require.True(t, withBuy.GreaterThanOrEqual(positionOnly), fmt.Sprintf("Test case %v:", i+1))
  1787  		}
  1788  		if tc.positionSize < 0 {
  1789  			require.True(t, withSell.LessThanOrEqual(positionOnly), fmt.Sprintf("Test case %v:", i+1))
  1790  		}
  1791  
  1792  		for _, o := range tc.buyOrders {
  1793  			if o.IsMarketOrder {
  1794  				o.Price = markPrice
  1795  			}
  1796  		}
  1797  
  1798  		for _, o := range tc.sellOrders {
  1799  			if o.IsMarketOrder {
  1800  				o.Price = markPrice
  1801  			}
  1802  		}
  1803  
  1804  		sort.Slice(tc.buyOrders, func(i, j int) bool {
  1805  			return tc.buyOrders[i].Price.GreaterThan(tc.buyOrders[j].Price)
  1806  		})
  1807  		sort.Slice(tc.sellOrders, func(i, j int) bool {
  1808  			return tc.sellOrders[i].Price.LessThan(tc.sellOrders[j].Price)
  1809  		})
  1810  
  1811  		newPositionSize := tc.positionSize
  1812  		mtmDelta := num.DecimalZero()
  1813  		lastMarkPrice := markPrice
  1814  		for _, o := range tc.buyOrders {
  1815  			if o.Price.LessThan(withBuy) {
  1816  				break
  1817  			}
  1818  			mtmDelta = mtmDelta.Add(num.DecimalFromInt64(newPositionSize).Mul(o.Price.Sub(lastMarkPrice)))
  1819  			newPositionSize += int64(o.TrueRemaining)
  1820  			lastMarkPrice = o.Price
  1821  		}
  1822  		collateralAfterMtm := collateral.Add(mtmDelta)
  1823  		liquidationPriceForNewPosition, _, _, err := risk.CalculateLiquidationPriceWithSlippageFactors(newPositionSize, nil, nil, lastMarkPrice, collateralAfterMtm, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne())
  1824  		require.NoError(t, err, fmt.Sprintf("Test case %v:", i+1))
  1825  		require.True(t, withBuy.Equal(liquidationPriceForNewPosition), fmt.Sprintf("Test case %v: withBuy=%s, newPositionOnly=%s", i+1, withBuy.String(), liquidationPriceForNewPosition.String()))
  1826  
  1827  		if tc.positionSize < 0 && newPositionSize > 0 {
  1828  			require.True(t, withBuy.LessThan(positionOnly), fmt.Sprintf("Test case %v:", i+1))
  1829  		}
  1830  
  1831  		marginAtLiquidationPrice := risk.CalculateMaintenanceMarginWithSlippageFactors(newPositionSize, nil, nil, liquidationPriceForNewPosition, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, num.DecimalZero(), false, num.DecimalZero())
  1832  		openVolume := num.DecimalFromInt64(newPositionSize).Div(positionFactor)
  1833  		mtmLoss := liquidationPriceForNewPosition.Sub(lastMarkPrice).Mul(openVolume)
  1834  		fundingLoss := num.MaxD(num.DecimalZero(), openVolume.Mul(constantPerUnitPositionSize)).Mul(num.DecimalFromFloat(-1))
  1835  		collateralAfterLoss := collateralAfterMtm.Add(mtmLoss).Add(fundingLoss)
  1836  
  1837  		if !marginAtLiquidationPrice.IsZero() {
  1838  			require.True(t, collateralAfterLoss.Div(marginAtLiquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: collateralAfterLoss=%s, marginAtLiquidationPrice:=%s", i+1, collateralAfterLoss, marginAtLiquidationPrice))
  1839  		} else {
  1840  			require.True(t, liquidationPriceForNewPosition.IsZero(), fmt.Sprintf("Test case %v:", i+1))
  1841  		}
  1842  
  1843  		newPositionSize = tc.positionSize
  1844  		mtmDelta = num.DecimalZero()
  1845  		lastMarkPrice = markPrice
  1846  		for _, o := range tc.sellOrders {
  1847  			if o.Price.GreaterThan(withSell) {
  1848  				break
  1849  			}
  1850  			mtmDelta = mtmDelta.Add(num.DecimalFromInt64(newPositionSize).Div(positionFactor).Mul(o.Price.Sub(lastMarkPrice)))
  1851  			newPositionSize -= int64(o.TrueRemaining)
  1852  			lastMarkPrice = o.Price
  1853  		}
  1854  		collateralAfterMtm = collateral.Add(mtmDelta)
  1855  		liquidationPriceForNewPosition, _, _, err = risk.CalculateLiquidationPriceWithSlippageFactors(newPositionSize, nil, nil, lastMarkPrice, collateralAfterMtm, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, constantPerUnitPositionSize, false, num.DecimalOne())
  1856  		require.NoError(t, err, fmt.Sprintf("Test case %v:", i+1))
  1857  		require.True(t, withSell.Equal(liquidationPriceForNewPosition), fmt.Sprintf("Test case %v: withSell=%s, newPositionOnly=%s", i+1, withSell.String(), liquidationPriceForNewPosition.String()))
  1858  
  1859  		if tc.positionSize > 0 && newPositionSize < 0 {
  1860  			require.True(t, withSell.GreaterThan(positionOnly), fmt.Sprintf("Test case %v:", i+1))
  1861  		}
  1862  
  1863  		// recalculate without funding loss and compensate for it when getting the expectation
  1864  		marginAtLiquidationPrice = risk.CalculateMaintenanceMarginWithSlippageFactors(newPositionSize, nil, nil, liquidationPriceForNewPosition, positionFactor, linearSlippageFactor, quadraticSlippageFactor, riskFactorLong, riskFactorShort, num.DecimalZero(), false, num.DecimalZero())
  1865  		openVolume = num.DecimalFromInt64(newPositionSize).Div(positionFactor)
  1866  		mtmLoss = liquidationPriceForNewPosition.Sub(lastMarkPrice).Mul(openVolume)
  1867  		fundingLoss = num.MaxD(num.DecimalZero(), openVolume.Mul(constantPerUnitPositionSize)).Mul(num.DecimalFromFloat(-1))
  1868  		collateralAfterLoss = collateralAfterMtm.Add(mtmLoss).Add(fundingLoss)
  1869  
  1870  		if !marginAtLiquidationPrice.IsZero() {
  1871  			require.True(t, collateralAfterLoss.Div(marginAtLiquidationPrice).Sub(num.DecimalOne()).Abs().LessThan(relativeTolerance), fmt.Sprintf("Test case %v: collateralAfterLoss=%s, marginAtLiquidationPrice:=%s", i+1, collateralAfterLoss, marginAtLiquidationPrice))
  1872  		} else {
  1873  			require.True(t, liquidationPriceForNewPosition.IsZero(), fmt.Sprintf("Test case %v:", i+1))
  1874  		}
  1875  	}
  1876  }
  1877  
  1878  func getTestEngine(t *testing.T, dp num.Decimal) *testEngine {
  1879  	t.Helper()
  1880  	cpy := riskFactors
  1881  	cpyPtr := &cpy
  1882  	ctrl := gomock.NewController(t)
  1883  	model := mocks.NewMockModel(ctrl)
  1884  	conf := risk.NewDefaultConfig()
  1885  	conf.StreamMarginLevelsVerbose = true
  1886  	ob := mocks.NewMockOrderbook(ctrl)
  1887  	ts := mocks.NewMockTimeService(ctrl)
  1888  	broker := bmocks.NewMockBroker(ctrl)
  1889  	as := mocks.NewMockAuctionState(ctrl)
  1890  	model.EXPECT().DefaultRiskFactors().Return(cpyPtr).Times(1)
  1891  	statevar := mocks.NewMockStateVarEngine(ctrl)
  1892  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
  1893  	statevar.EXPECT().NewEvent(gomock.Any(), gomock.Any(), gomock.Any())
  1894  	engine := risk.NewEngine(logging.NewTestLogger(),
  1895  		conf,
  1896  		getMarginCalculator(),
  1897  		model,
  1898  		ob,
  1899  		as,
  1900  		ts,
  1901  		broker,
  1902  		"mktid",
  1903  		"ETH",
  1904  		statevar,
  1905  		dp,
  1906  		false,
  1907  		nil,
  1908  		DefaultSlippageFactor,
  1909  		DefaultSlippageFactor,
  1910  	)
  1911  
  1912  	return &testEngine{
  1913  		Engine:    engine,
  1914  		ctrl:      ctrl,
  1915  		model:     model,
  1916  		orderbook: ob,
  1917  		tsvc:      ts,
  1918  		broker:    broker,
  1919  		as:        as,
  1920  	}
  1921  }
  1922  
  1923  func getMarginCalculator() *types.MarginCalculator {
  1924  	return &types.MarginCalculator{
  1925  		ScalingFactors: &types.ScalingFactors{
  1926  			SearchLevel:       num.DecimalFromFloat(1.1),
  1927  			InitialMargin:     num.DecimalFromFloat(1.2),
  1928  			CollateralRelease: num.DecimalFromFloat(1.4),
  1929  		},
  1930  	}
  1931  }
  1932  
  1933  func (m testMargin) AverageEntryPrice() *num.Uint {
  1934  	absSize := m.size
  1935  	if absSize < 0 {
  1936  		absSize = -absSize
  1937  	}
  1938  	return num.UintZero().Mul(m.Price(), num.NewUint(uint64(absSize)))
  1939  }
  1940  
  1941  func (m testMargin) Party() string {
  1942  	return m.party
  1943  }
  1944  
  1945  func (m testMargin) MarketID() string {
  1946  	return m.market
  1947  }
  1948  
  1949  func (m testMargin) Asset() string {
  1950  	return m.asset
  1951  }
  1952  
  1953  func (m testMargin) MarginBalance() *num.Uint {
  1954  	return num.NewUint(m.margin)
  1955  }
  1956  
  1957  func (m testMargin) OrderMarginBalance() *num.Uint {
  1958  	return num.NewUint(m.orderMargin)
  1959  }
  1960  
  1961  func (m testMargin) GeneralBalance() *num.Uint {
  1962  	return num.NewUint(m.general)
  1963  }
  1964  
  1965  func (m testMargin) GeneralAccountBalance() *num.Uint {
  1966  	return num.NewUint(m.general)
  1967  }
  1968  
  1969  func (m testMargin) BondBalance() *num.Uint {
  1970  	return num.UintZero()
  1971  }
  1972  
  1973  func (m testMargin) Price() *num.Uint {
  1974  	return num.NewUint(m.price)
  1975  }
  1976  
  1977  func (m testMargin) Buy() int64 {
  1978  	return m.buy
  1979  }
  1980  
  1981  func (m testMargin) Sell() int64 {
  1982  	return m.sell
  1983  }
  1984  
  1985  func (m testMargin) Size() int64 {
  1986  	return m.size
  1987  }
  1988  
  1989  func (m testMargin) BuySumProduct() *num.Uint {
  1990  	return num.NewUint(m.buySumProduct)
  1991  }
  1992  
  1993  func (m testMargin) SellSumProduct() *num.Uint {
  1994  	return num.NewUint(m.sellSumProduct)
  1995  }
  1996  
  1997  func (m testMargin) VWBuy() *num.Uint {
  1998  	if m.buy == 0 {
  1999  		num.UintZero()
  2000  	}
  2001  	return num.UintZero().Div(m.BuySumProduct(), num.NewUint(uint64(m.buy)))
  2002  }
  2003  
  2004  func (m testMargin) VWSell() *num.Uint {
  2005  	if m.sell == 0 {
  2006  		num.UintZero()
  2007  	}
  2008  	return num.UintZero().Div(m.SellSumProduct(), num.NewUint(uint64(m.sell)))
  2009  }
  2010  
  2011  func (m testMargin) ClearPotentials() {}
  2012  
  2013  func (m testMargin) Transfer() *types.Transfer {
  2014  	return m.transfer
  2015  }
  2016  
  2017  func (m testMargin) MarginShortFall() *num.Uint {
  2018  	return num.NewUint(m.marginShortFall)
  2019  }