code.vegaprotocol.io/vega@v0.79.0/core/execution/common/mark_price_utils_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 common_test
    17  
    18  import (
    19  	"testing"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/execution/common"
    23  	"code.vegaprotocol.io/vega/core/matching"
    24  	"code.vegaprotocol.io/vega/core/types"
    25  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    26  	"code.vegaprotocol.io/vega/libs/num"
    27  	"code.vegaprotocol.io/vega/logging"
    28  
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestMarkPriceByWeights(t *testing.T) {
    33  	// no mark prices
    34  	require.Nil(t, common.CompositePriceByWeight(nil, []num.Decimal{num.NewDecimalFromFloat(0.2), num.NewDecimalFromFloat(0.5)}, []int64{3, 2}, []time.Duration{0, 0}, 10))
    35  
    36  	// no non-stale mark prices
    37  	// time now is 10, both timestamps of prices are 2,3 with delta = 0 for both
    38  	require.Nil(t, common.CompositePriceByWeight([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []num.Decimal{num.NewDecimalFromFloat(0.2), num.NewDecimalFromFloat(0.5)}, []int64{3, 2}, []time.Duration{0, 0}, 10))
    39  
    40  	// only the first price is non stale
    41  	// 10-8<=2
    42  	require.Equal(t, num.NewUint(100), common.CompositePriceByWeight([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []num.Decimal{num.NewDecimalFromFloat(0.2), num.NewDecimalFromFloat(0.5)}, []int64{8, 2}, []time.Duration{2, 0}, 10))
    43  
    44  	// only the second price is non stale
    45  	// 10-7<=3
    46  	require.Equal(t, num.NewUint(80), common.CompositePriceByWeight([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []num.Decimal{num.NewDecimalFromFloat(0.2), num.NewDecimalFromFloat(0.5)}, []int64{8, 7}, []time.Duration{1, 3}, 10))
    47  
    48  	// both prices are eligible use weights:
    49  	// 0.4*100+0.6*80 = 88
    50  	require.Equal(t, num.NewUint(88), common.CompositePriceByWeight([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []num.Decimal{num.NewDecimalFromFloat(0.2), num.NewDecimalFromFloat(0.3)}, []int64{8, 7}, []time.Duration{2, 3}, 10))
    51  }
    52  
    53  func TestMarkPriceByMedian(t *testing.T) {
    54  	// no prices
    55  	require.Nil(t, common.CompositePriceByMedian(nil, []int64{3, 2}, []time.Duration{0, 0}, 10))
    56  
    57  	// no non-stale mark prices
    58  	// time now is 10, both timestamps of prices are 2,3 with delta = 0 for both
    59  	require.Nil(t, common.CompositePriceByMedian([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []int64{3, 2}, []time.Duration{0, 0}, 10))
    60  
    61  	// only the first price is non stale
    62  	// 10-8<=2
    63  	require.Equal(t, num.NewUint(100), common.CompositePriceByMedian([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []int64{8, 2}, []time.Duration{2, 0}, 10))
    64  
    65  	// only the second price is non stale
    66  	// 10-7<=3
    67  	require.Equal(t, num.NewUint(80), common.CompositePriceByMedian([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []int64{8, 7}, []time.Duration{1, 3}, 10))
    68  
    69  	// both prices are non stale, median is calculated (average in this even case)
    70  	require.Equal(t, num.NewUint(90), common.CompositePriceByMedian([]*num.Uint{num.NewUint(100), num.NewUint(80)}, []int64{8, 7}, []time.Duration{2, 3}, 10))
    71  
    72  	// all prices are non stale, median is calculated
    73  	require.Equal(t, num.NewUint(99), common.CompositePriceByMedian([]*num.Uint{num.NewUint(99), num.NewUint(100), num.NewUint(80)}, []int64{8, 8, 7}, []time.Duration{2, 2, 3}, 10))
    74  }
    75  
    76  func TestMedianMarkPrice(t *testing.T) {
    77  	require.Nil(t, common.MedianPrice(nil))
    78  	require.Equal(t, "100", common.MedianPrice([]*num.Uint{num.NewUint(110), num.NewUint(99), num.NewUint(100)}).String())
    79  	require.Equal(t, "100", common.MedianPrice([]*num.Uint{num.NewUint(110), num.NewUint(101), num.NewUint(99), num.NewUint(100)}).String())
    80  }
    81  
    82  func TestMarkPriceFromTrades(t *testing.T) {
    83  	alpha := num.DecimalZero()
    84  	decayPower := num.DecimalZero()
    85  	lambda := num.NewDecimalFromFloat(100)
    86  
    87  	trade1 := &types.Trade{
    88  		Price:     num.NewUint(129),
    89  		Size:      10,
    90  		Timestamp: 120,
    91  	}
    92  
    93  	trade2 := &types.Trade{
    94  		Price:     num.NewUint(124),
    95  		Size:      40,
    96  		Timestamp: 150,
    97  	}
    98  	trade3 := &types.Trade{
    99  		Price:     num.NewUint(133),
   100  		Size:      50,
   101  		Timestamp: 200,
   102  	}
   103  
   104  	// given alpha is 0, the time_weight is 1
   105  	// the total size is 60, so trade weights are:
   106  	// 1/10, 4/10, 5/10
   107  	// so the markPrice = 0.1*129 + 0.4*124 + 0.5 * 133 = 129
   108  	mp := common.PriceFromTrades([]*types.Trade{trade1, trade2, trade3}, alpha, lambda, decayPower, 200)
   109  	require.Equal(t, "129", mp.String())
   110  
   111  	// now lets repeat with non zero alpha
   112  	alpha = num.DecimalFromFloat(0.2)
   113  	decayPower = num.DecimalOne()
   114  
   115  	// given alpha is 0, the time_weight is 1
   116  	// the total size is 60, so trade weights are:
   117  	// 1/10 * (1 - 0.2 * (200-120)/100) = 0.084
   118  	// 4/10 * (1 - 0.2 * (200-150)/100) = 0.36
   119  	// 5/10 * (1 - 0.2 * (200-200)/100) = 0.5
   120  	// total weight = 0.944
   121  	// mp = (0.084 * 129 + 0.36 * 124 + 0.5 * 133)/0.944 = 172.1276595745
   122  	mp = common.PriceFromTrades([]*types.Trade{trade1, trade2, trade3}, alpha, lambda, decayPower, 200)
   123  	require.Equal(t, "129", mp.String())
   124  }
   125  
   126  func TestPBookAtTimeT(t *testing.T) {
   127  	book := matching.NewCachedOrderBook(logging.NewTestLogger(), matching.NewDefaultConfig(), "market1", false, func(int64) {})
   128  	C := num.NewUint(1000)
   129  	initialScaling := num.DecimalFromFloat(0.2)
   130  	slippage := num.DecimalFromFloat(0.1)
   131  	shortRisk := num.DecimalFromFloat(0.3)
   132  	longRisk := num.DecimalFromFloat(0.4)
   133  
   134  	// empty book
   135  	require.Nil(t, common.PriceFromBookAtTime(C, initialScaling, slippage, shortRisk, longRisk, book))
   136  
   137  	// no bids
   138  	_, err := book.SubmitOrder(newOrder(num.NewUint(120), 10, types.SideSell))
   139  	require.NoError(t, err)
   140  	require.Nil(t, common.PriceFromBookAtTime(C, initialScaling, slippage, shortRisk, longRisk, book))
   141  	book.CancelAllOrders("party1")
   142  
   143  	// no asks
   144  	_, err = book.SubmitOrder(newOrder(num.NewUint(125), 10, types.SideBuy))
   145  	require.NoError(t, err)
   146  	require.Nil(t, common.PriceFromBookAtTime(C, initialScaling, slippage, shortRisk, longRisk, book))
   147  
   148  	// orders on both sides
   149  	_, err = book.SubmitOrder(newOrder(num.NewUint(200), 10, types.SideSell))
   150  	require.NoError(t, err)
   151  
   152  	// N_buy = 1000 / ((0.2) * (0.1+0.3)) = 12500
   153  	// N_sell = 1000 / ((0.2) * (0.1+0.4)) = 10000
   154  	// V_buy = N_buy/best_bid = 12500/125 = 100
   155  	// V_sell = N_sell/best_ask = 10000/200 = 50
   156  	// insufficient volume in the book for both sides
   157  
   158  	require.Nil(t, common.PriceFromBookAtTime(C, initialScaling, slippage, shortRisk, longRisk, book))
   159  
   160  	// add orders on both sides
   161  	_, err = book.SubmitOrder(newOrder(num.NewUint(200), 40, types.SideSell))
   162  	require.NoError(t, err)
   163  	_, err = book.SubmitOrder(newOrder(num.NewUint(125), 90, types.SideBuy))
   164  	require.NoError(t, err)
   165  
   166  	// (125+200)/2 = 162
   167  	require.Equal(t, "162", common.PriceFromBookAtTime(C, initialScaling, slippage, shortRisk, longRisk, book).String())
   168  }
   169  
   170  func TestCalculateTimeWeightedAverageBookMarkPrice(t *testing.T) {
   171  	timeToPrice := map[int64]*num.Uint{0: num.NewUint(100), 30: num.NewUint(120), 45: num.NewUint(150)}
   172  
   173  	// 100 * 30/60 + 120 * 15/60 + 150 * 15/60 = 117.5 => 117
   174  	require.Equal(t, "117", common.CalculateTimeWeightedAverageBookPrice(timeToPrice, 60, 60).String())
   175  
   176  	// 120 * 15/30 + 150 * 15/30 = 97.5 => 135
   177  	require.Equal(t, "135", common.CalculateTimeWeightedAverageBookPrice(timeToPrice, 60, 30).String())
   178  
   179  	// 100 * 30/120 + 120 * 15/120 + 150 * 75/120 = 133.75 => 133
   180  	require.Equal(t, "133", common.CalculateTimeWeightedAverageBookPrice(timeToPrice, 120, 120).String())
   181  
   182  	// only the price from 45 is considered as the price from 30 is starting before the mark price period
   183  	require.Equal(t, "150", common.CalculateTimeWeightedAverageBookPrice(timeToPrice, 120, 80).String())
   184  }
   185  
   186  func newOrder(price *num.Uint, size uint64, side types.Side) *types.Order {
   187  	return &types.Order{
   188  		ID:            vgcrypto.RandomHash(),
   189  		Status:        types.OrderStatusActive,
   190  		Type:          types.OrderTypeLimit,
   191  		MarketID:      "market1",
   192  		Party:         "party1",
   193  		Side:          side,
   194  		Price:         price,
   195  		OriginalPrice: price,
   196  		Size:          size,
   197  		Remaining:     size,
   198  		TimeInForce:   types.OrderTimeInForceGTC,
   199  	}
   200  }