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 }