code.vegaprotocol.io/vega@v0.79.0/core/liquidity/supplied/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 supplied_test
    17  
    18  import (
    19  	"testing"
    20  
    21  	"code.vegaprotocol.io/vega/core/integration/stubs"
    22  	"code.vegaprotocol.io/vega/core/liquidity/supplied"
    23  	"code.vegaprotocol.io/vega/core/liquidity/supplied/mocks"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	"code.vegaprotocol.io/vega/core/types/statevar"
    26  	"code.vegaprotocol.io/vega/libs/num"
    27  	"code.vegaprotocol.io/vega/logging"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  var (
    34  	MarkPrice                          = num.NewUint(103)
    35  	MarkPriceD                         = MarkPrice.ToDecimal()
    36  	DefaultInRangeProbabilityOfTrading = num.DecimalFromFloat(.5)
    37  	Horizon                            = num.DecimalFromFloat(0.001)
    38  	TickSize                           = num.NewUint(1)
    39  )
    40  
    41  func TestLiquidityScore(t *testing.T) {
    42  	minLpPrice, maxLpPrice := num.UintOne(), num.MaxUint()
    43  	ctrl := gomock.NewController(t)
    44  	defer ctrl.Finish()
    45  	riskModel := mocks.NewMockRiskModel(ctrl)
    46  	priceMonitor := mocks.NewMockPriceMonitor(ctrl)
    47  	riskModel.EXPECT().GetProjectionHorizon().Return(Horizon).Times(1)
    48  
    49  	minPMPrice := num.NewWrappedDecimal(num.NewUint(89), num.DecimalFromInt64(89))
    50  	maxPMPrice := num.NewWrappedDecimal(num.NewUint(111), num.DecimalFromInt64(111))
    51  
    52  	// No orders
    53  	priceMonitor.EXPECT().GetValidPriceRange().Return(minPMPrice, maxPMPrice).AnyTimes()
    54  	statevarEngine := stubs.NewStateVar()
    55  	engine := supplied.NewEngine(riskModel, priceMonitor, "asset1", "market1", statevarEngine, logging.NewTestLogger(), num.DecimalFromInt64(1))
    56  	require.NotNil(t, engine)
    57  
    58  	f := func() (num.Decimal, num.Decimal, error) { return MarkPriceD, MarkPriceD, nil }
    59  	engine.SetGetStaticPricesFunc(f)
    60  
    61  	liquidity := engine.CalculateLiquidityScore([]*types.Order{}, MarkPriceD, MarkPriceD, minLpPrice, maxLpPrice)
    62  	require.True(t, liquidity.IsZero())
    63  
    64  	// 1 buy, no sells
    65  	buyOrder1 := &types.Order{
    66  		Price:     num.NewUint(102),
    67  		Size:      30,
    68  		Remaining: 25,
    69  		Side:      types.SideBuy,
    70  	}
    71  
    72  	buyOrder1Prob := num.DecimalFromFloat(0.256)
    73  	sellOrder1Prob := num.DecimalFromFloat(0.33)
    74  	sellOrder2Prob := num.DecimalFromFloat(0.17)
    75  
    76  	sellOrder1 := &types.Order{
    77  		Price:     num.NewUint(105),
    78  		Size:      15,
    79  		Remaining: 11,
    80  		Side:      types.SideSell,
    81  	}
    82  	sellOrder2 := &types.Order{
    83  		Price:     num.NewUint(104),
    84  		Size:      60,
    85  		Remaining: 60,
    86  		Side:      types.SideSell,
    87  	}
    88  
    89  	riskModel.EXPECT().ProbabilityOfTrading(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(func(best, order, min num.Decimal, max num.Decimal, yFrac num.Decimal, isBid bool, applyMinMax bool) num.Decimal {
    90  		if best.Equal(MarkPriceD) && order.Sub(buyOrder1.Price.ToDecimal()).Abs().LessThanOrEqual(num.DecimalFromFloat(0.1)) && isBid {
    91  			return buyOrder1Prob
    92  		}
    93  		if best.Equal(MarkPriceD) && order.Sub(sellOrder1.Price.ToDecimal()).Abs().LessThanOrEqual(num.DecimalFromFloat(0.1)) && !isBid {
    94  			return sellOrder1Prob
    95  		}
    96  		if best.Equal(MarkPriceD) && order.Sub(sellOrder2.Price.ToDecimal()).Abs().LessThanOrEqual(num.DecimalFromFloat(0.1)) && !isBid {
    97  			return sellOrder2Prob
    98  		}
    99  		if order.LessThanOrEqual(num.DecimalZero()) {
   100  			return num.DecimalZero()
   101  		}
   102  		if order.GreaterThanOrEqual(num.DecimalFromInt64(2).Mul(best)) {
   103  			return num.DecimalZero()
   104  		}
   105  		return num.DecimalFromFloat(0.5)
   106  	})
   107  
   108  	statevarEngine.NewEvent("asset1", "market1", statevar.EventTypeAuctionEnded)
   109  	liquidity2 := engine.CalculateLiquidityScore([]*types.Order{}, MarkPriceD, MarkPriceD, minLpPrice, maxLpPrice)
   110  	require.True(t, liquidity2.IsZero())
   111  
   112  	buyOrder1Size := num.DecimalFromInt64(int64(buyOrder1.Remaining))
   113  	buyLiquidityScore := buyOrder1Prob.Mul(DefaultInRangeProbabilityOfTrading).Mul(buyOrder1Size)
   114  	buySideTotalSize := num.DecimalZero().Add(buyOrder1Size)
   115  
   116  	buyLiquidityScore = buyLiquidityScore.Div(buySideTotalSize)
   117  
   118  	sellOrder1Size := num.DecimalFromInt64(int64(sellOrder1.Remaining))
   119  	sellLiquidityScore := sellOrder1Prob.Mul(DefaultInRangeProbabilityOfTrading).Mul(sellOrder1Size)
   120  	sellSideTotalSize := num.DecimalZero().Add(sellOrder1Size)
   121  
   122  	sellOrder2Size := num.DecimalFromInt64(int64(sellOrder2.Remaining))
   123  	sellLiquidityScore = sellLiquidityScore.Add(sellOrder2Prob.Mul(DefaultInRangeProbabilityOfTrading).Mul(sellOrder2Size))
   124  	sellSideTotalSize = sellSideTotalSize.Add(sellOrder2Size)
   125  
   126  	sellLiquidityScore = sellLiquidityScore.Div(sellSideTotalSize)
   127  
   128  	expectedScore := min(buyLiquidityScore, sellLiquidityScore)
   129  	liquidity3 := engine.CalculateLiquidityScore([]*types.Order{buyOrder1, sellOrder1, sellOrder2}, MarkPriceD, MarkPriceD, minLpPrice, maxLpPrice)
   130  	require.True(t, expectedScore.Equal(liquidity3))
   131  
   132  	// 2 buys, 2 sells
   133  	buyOrder2 := &types.Order{
   134  		Price:     num.NewUint(102),
   135  		Size:      600,
   136  		Remaining: 599,
   137  		Side:      types.SideBuy,
   138  	}
   139  	buyOrder2Prob := num.DecimalFromFloat(0.256)
   140  
   141  	//	buyLiquidity += buyOrder2.Price.Float64() * float64(buyOrder2.Remaining) * buyOrder2Prob
   142  	buyOrder2Size := num.DecimalFromInt64(int64(buyOrder2.Remaining))
   143  	buyLiquidityScore = buyOrder1Prob.Mul(DefaultInRangeProbabilityOfTrading).Mul(buyOrder1Size).Add(buyOrder2Prob.Mul(DefaultInRangeProbabilityOfTrading).Mul(buyOrder2Size))
   144  	buySideTotalSize = num.DecimalZero().Add(buyOrder1Size).Add(buyOrder2Size)
   145  
   146  	buyLiquidityScore = buyLiquidityScore.Div(buySideTotalSize)
   147  
   148  	expectedScore = min(buyLiquidityScore, sellLiquidityScore)
   149  	liquidity4 := engine.CalculateLiquidityScore([]*types.Order{buyOrder1, sellOrder1, sellOrder2, buyOrder2}, MarkPriceD, MarkPriceD, minLpPrice, maxLpPrice)
   150  	require.True(t, expectedScore.Equal(liquidity4))
   151  
   152  	// Orders outside PM range (but within LP range)
   153  
   154  	// add orders outwith the PM bounds
   155  	buyOrder3 := &types.Order{
   156  		Price:     num.UintZero().Sub(minPMPrice.Representation(), num.UintOne()),
   157  		Size:      123,
   158  		Remaining: 45,
   159  		Side:      types.SideBuy,
   160  	}
   161  	sellOrder3 := &types.Order{
   162  		Price:     num.UintZero().Add(maxPMPrice.Representation(), num.UintOne()),
   163  		Size:      345,
   164  		Remaining: 67,
   165  		Side:      types.SideSell,
   166  	}
   167  
   168  	// liquidity should drop as the volume-weighted PoT of trading within the LP range drops (some orders included in the score now have PoT==0)
   169  	liquidity5 := engine.CalculateLiquidityScore([]*types.Order{buyOrder1, sellOrder1, sellOrder2, buyOrder2, sellOrder3, buyOrder3}, MarkPriceD, MarkPriceD, minLpPrice, maxLpPrice)
   170  	require.True(t, liquidity5.LessThan(liquidity4))
   171  
   172  	// Orders outside LP range (but within PM range)
   173  
   174  	// set bounds at prices of orders furtherst away form the mid
   175  	minLpPrice = buyOrder2.Price
   176  	maxLpPrice = sellOrder1.Price
   177  
   178  	// add orders outwith the LP bounds
   179  	buyOrder3 = &types.Order{
   180  		Price:     num.UintZero().Sub(minLpPrice, num.UintOne()),
   181  		Size:      123,
   182  		Remaining: 45,
   183  		Side:      types.SideBuy,
   184  	}
   185  	sellOrder3 = &types.Order{
   186  		Price:     num.UintZero().Add(maxLpPrice, num.UintOne()),
   187  		Size:      345,
   188  		Remaining: 67,
   189  		Side:      types.SideSell,
   190  	}
   191  
   192  	// liquidity shouldn't change
   193  	liquidity6 := engine.CalculateLiquidityScore([]*types.Order{buyOrder1, sellOrder1, sellOrder2, buyOrder2, sellOrder3, buyOrder3}, MarkPriceD, MarkPriceD, minLpPrice, maxLpPrice)
   194  	require.Equal(t, liquidity4, liquidity6)
   195  }
   196  
   197  func min(d1, d2 num.Decimal) num.Decimal {
   198  	if d1.LessThan(d2) {
   199  		return d1
   200  	}
   201  	return d2
   202  }