code.vegaprotocol.io/vega@v0.79.0/core/liquidity/v2/scores_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 liquidity_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/idgeneration"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/libs/crypto"
    26  	"code.vegaprotocol.io/vega/libs/num"
    27  
    28  	"github.com/golang/mock/gomock"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestLiquidityScoresMechanics(t *testing.T) {
    33  	var (
    34  		party1     = "party-1"
    35  		party2     = "party-2"
    36  		party3     = "party-3"
    37  		party4     = "party-4"
    38  		ctx        = context.Background()
    39  		now        = time.Now()
    40  		tng        = newTestEngine(t)
    41  		bestBid    = num.NewDecimalFromFloat(95)
    42  		bestAsk    = num.NewDecimalFromFloat(105)
    43  		minLpPrice = num.NewUint(90)
    44  		maxLpPrice = num.NewUint(110)
    45  		minPmPrice = num.NewWrappedDecimal(num.NewUint(85), num.DecimalFromFloat(85))
    46  		maxPmPrice = num.NewWrappedDecimal(num.NewUint(115), num.DecimalFromFloat(115))
    47  		commitment = 1000000
    48  	)
    49  	defer tng.ctrl.Finish()
    50  	tng.priceMonitor.EXPECT().GetValidPriceRange().AnyTimes().Return(minPmPrice, maxPmPrice).AnyTimes()
    51  	tng.auctionState.EXPECT().IsOpeningAuction().Return(false).AnyTimes()
    52  
    53  	gomock.InOrder(
    54  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestBid, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.5)),
    55  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestBid, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.4)),
    56  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestBid, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.3)),
    57  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestBid, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.2)),
    58  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestBid, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.1)),
    59  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestBid, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.0)),
    60  	)
    61  	gomock.InOrder(
    62  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestAsk, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.5)),
    63  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestAsk, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.4)),
    64  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestAsk, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.3)),
    65  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestAsk, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.2)),
    66  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestAsk, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.1)),
    67  		tng.riskModel.EXPECT().ProbabilityOfTrading(bestAsk, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(num.DecimalFromFloat(0.0)),
    68  	)
    69  
    70  	// We don't care about the following calls
    71  	tng.broker.EXPECT().Send(gomock.Any()).AnyTimes()
    72  
    73  	tng.auctionState.EXPECT().InAuction().Return(false).AnyTimes()
    74  
    75  	zero := num.UintZero()
    76  
    77  	tng.orderbook.EXPECT().GetBestStaticBidPrice().Return(zero, nil).AnyTimes()
    78  	tng.orderbook.EXPECT().GetBestStaticAskPrice().Return(zero, nil).AnyTimes()
    79  
    80  	// initialise PoT
    81  	tng.engine.SetGetStaticPricesFunc(func() (num.Decimal, num.Decimal, error) { return bestBid, bestAsk, nil })
    82  	tng.stateVar.OnTick(ctx, now)
    83  	require.True(t, tng.engine.IsProbabilityOfTradingInitialised())
    84  
    85  	idgen := idgeneration.New(crypto.RandomHash())
    86  
    87  	partyOneOrders := []*types.Order{
    88  		{Side: types.SideBuy, Price: num.NewUint(98), Size: 5103},
    89  		{Side: types.SideBuy, Price: num.NewUint(93), Size: 5377},
    90  		{Side: types.SideSell, Price: num.NewUint(102), Size: 4902},
    91  		{Side: types.SideSell, Price: num.NewUint(107), Size: 4673},
    92  	}
    93  
    94  	// party1 submission
    95  	tng.submitLiquidityProvisionAndCreateOrders(t, ctx, party1, commitment, idgen, partyOneOrders)
    96  
    97  	cLiq1, t1 := tng.engine.GetCurrentLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
    98  	require.Len(t, cLiq1, 1)
    99  	require.True(t, t1.GreaterThan(num.DecimalZero()))
   100  
   101  	tng.engine.UpdateAverageLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   102  	lScores1 := tng.engine.GetAverageLiquidityScores()
   103  	require.Len(t, lScores1, 1)
   104  	lScoresSumTo1(t, lScores1)
   105  
   106  	// party2 submission with 3*commitment
   107  	partyTwoOrders := []*types.Order{
   108  		{Side: types.SideBuy, Price: num.NewUint(98), Size: 15307},
   109  		{Side: types.SideBuy, Price: num.NewUint(93), Size: 16130},
   110  		{Side: types.SideSell, Price: num.NewUint(102), Size: 14706},
   111  		{Side: types.SideSell, Price: num.NewUint(107), Size: 14019},
   112  	}
   113  
   114  	tng.submitLiquidityProvisionAndCreateOrders(t, ctx, party2, 3*commitment, idgen, partyTwoOrders)
   115  
   116  	cLiq2, t2 := tng.engine.GetCurrentLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   117  	require.Len(t, cLiq2, 2)
   118  	require.True(t, t2.GreaterThan(num.DecimalZero()))
   119  
   120  	p1cLiq := cLiq2[party1].Copy()
   121  	p2cLiqExp := p1cLiq.Mul(num.DecimalFromFloat(3))
   122  	// there's some ceiling going on when creating order volumes from commitment so check results within delta
   123  	expFP, _ := p2cLiqExp.Float64()
   124  	actFP, _ := cLiq2[party2].Float64()
   125  	require.InDelta(t, expFP, actFP, 1e-4*float64(commitment))
   126  
   127  	tng.engine.UpdateAverageLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   128  	lScores2 := tng.engine.GetAverageLiquidityScores()
   129  	require.Len(t, lScores2, 2)
   130  	lScoresSumTo1(t, lScores2)
   131  
   132  	// party3 submission with 3*offset
   133  	partyThreeOrders := []*types.Order{
   134  		{Side: types.SideBuy, Price: num.NewUint(94), Size: 5320},
   135  		{Side: types.SideBuy, Price: num.NewUint(89), Size: 5618},
   136  		{Side: types.SideSell, Price: num.NewUint(106), Size: 4717},
   137  		{Side: types.SideSell, Price: num.NewUint(111), Size: 4505},
   138  	}
   139  
   140  	tng.submitLiquidityProvisionAndCreateOrders(t, ctx, party3, commitment, idgen, partyThreeOrders)
   141  
   142  	cLiq3, t3 := tng.engine.GetCurrentLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   143  	require.Len(t, cLiq3, 3)
   144  	require.True(t, t3.GreaterThan(num.DecimalZero()))
   145  	require.True(t, cLiq3[party1].GreaterThan(cLiq3[party3]))
   146  
   147  	tng.engine.UpdateAverageLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   148  	lScores3 := tng.engine.GetAverageLiquidityScores()
   149  	require.Len(t, lScores3, 3)
   150  	lScoresSumTo1(t, lScores3)
   151  
   152  	// now add 1 LP, remove 1 LP and change
   153  	//    remove party3
   154  	require.NoError(t, tng.engine.CancelLiquidityProvision(ctx, party3))
   155  
   156  	//    add same submission as party3, but by party4
   157  	tng.submitLiquidityProvisionAndCreateOrders(t, ctx, party4, commitment, idgen, partyThreeOrders)
   158  
   159  	cLiq4, t4 := tng.engine.GetCurrentLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   160  	require.Len(t, cLiq4, 3)
   161  	require.True(t, t4.GreaterThan(num.DecimalZero()))
   162  	// should get same value for party4 as for party3 in previous round
   163  	require.True(t, cLiq4[party4].Equal(cLiq3[party3]))
   164  
   165  	tng.engine.UpdateAverageLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   166  	lScores4 := tng.engine.GetAverageLiquidityScores()
   167  	require.Len(t, lScores4, 3)
   168  	lScoresSumTo1(t, lScores4)
   169  
   170  	keys := make([]string, 0, len(lScores4))
   171  	for k := range lScores4 {
   172  		keys = append(keys, k)
   173  	}
   174  	activeParties := []string{party1, party2, party4}
   175  	require.ElementsMatch(t, activeParties, keys)
   176  }
   177  
   178  func (tng *testEngine) submitLiquidityProvisionAndCreateOrders(
   179  	t *testing.T,
   180  	ctx context.Context,
   181  	party string,
   182  	commitment int,
   183  	idgen *idgeneration.IDGenerator,
   184  	orders []*types.Order,
   185  ) {
   186  	t.Helper()
   187  
   188  	lps := &types.LiquidityProvisionSubmission{
   189  		MarketID:         tng.marketID,
   190  		CommitmentAmount: num.NewUint(uint64(commitment)),
   191  		Fee:              num.DecimalFromFloat(0.5),
   192  	}
   193  
   194  	_, err := tng.engine.SubmitLiquidityProvision(ctx, lps, party, idgeneration.New(crypto.RandomHash()))
   195  	require.NoError(t, err)
   196  
   197  	price := num.NewUint(100)
   198  	now := tng.tsvc.GetTimeNow()
   199  	tng.engine.ResetSLAEpoch(now, price, price, num.DecimalOne())
   200  	tng.engine.ApplyPendingProvisions(ctx, now)
   201  
   202  	for _, o := range orders {
   203  		o.ID = idgen.NextID()
   204  		o.MarketID = tng.marketID
   205  		o.TimeInForce = types.OrderTimeInForceGTC
   206  		o.Type = types.OrderTypeLimit
   207  		o.Status = types.OrderStatusActive
   208  		o.Remaining = o.Size
   209  	}
   210  
   211  	require.Equal(t, types.LiquidityProvisionStatusActive, tng.engine.LiquidityProvisionByPartyID(party).Status)
   212  	tng.orderbook.EXPECT().GetOrdersPerParty(party).Return(orders).AnyTimes()
   213  }
   214  
   215  func lScoresSumTo1(t *testing.T, lScores map[string]num.Decimal) {
   216  	t.Helper()
   217  
   218  	goTo0 := num.DecimalOne()
   219  	for _, v := range lScores {
   220  		goTo0 = goTo0.Sub(v)
   221  	}
   222  
   223  	zeroFp, _ := goTo0.Float64()
   224  
   225  	require.InDelta(t, 0, zeroFp, 1e-8)
   226  }