code.vegaprotocol.io/vega@v0.79.0/core/execution/common/mark_price_utils.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
    17  
    18  import (
    19  	"sort"
    20  	"time"
    21  
    22  	"code.vegaprotocol.io/vega/core/matching"
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  )
    26  
    27  // timeWeight calculates the time weight for the given trade time given the current time.
    28  func timeWeight(alpha, lambda, decayPower num.Decimal, t, tradeTime int64) num.Decimal {
    29  	if alpha.IsZero() {
    30  		return num.DecimalOne()
    31  	}
    32  	timeFraction := num.DecimalFromInt64(t - tradeTime).Div(lambda)
    33  	res := num.DecimalOne().Sub(alpha.Mul(timeFraction.Pow(decayPower)))
    34  	if res.IsNegative() {
    35  		return num.DecimalZero()
    36  	}
    37  	return res
    38  }
    39  
    40  // PriceFromTrades calculates the mark price from trades in the current price frequency.
    41  func PriceFromTrades(trades []*types.Trade, decayWeight, lambda, decayPower num.Decimal, t int64) *num.Uint {
    42  	if lambda.IsZero() {
    43  		return nil
    44  	}
    45  	wSum := num.DecimalZero()
    46  	ptSum := num.DecimalZero()
    47  
    48  	totalTradedSize := int64(0)
    49  	for _, trade := range trades {
    50  		totalTradedSize += int64(trade.Size)
    51  	}
    52  	totalTradedSizeD := num.DecimalFromInt64(totalTradedSize)
    53  
    54  	for _, trade := range trades {
    55  		weightedSize := timeWeight(decayWeight, lambda, decayPower, t, trade.Timestamp).Mul(num.DecimalFromInt64(int64(trade.Size)).Div(totalTradedSizeD))
    56  		wSum = wSum.Add(weightedSize)
    57  		ptSum = ptSum.Add(weightedSize.Mul(trade.Price.ToDecimal()))
    58  	}
    59  	// if all trades have time weight 0, there's no price from trades.
    60  	if wSum.IsZero() {
    61  		return nil
    62  	}
    63  	ptUint, _ := num.UintFromDecimal(ptSum.Div(wSum))
    64  	return ptUint
    65  }
    66  
    67  // PriceFromBookAtTime calculate the mark price as the average price of buying/selling the quantity implied by scaling C
    68  // by the factors. If there is no bid or ask price for the required quantity, returns nil.
    69  func PriceFromBookAtTime(C *num.Uint, initialScalingFactor, slippageFactor, shortRiskFactor, longRiskFactor num.Decimal, orderBook *matching.CachedOrderBook) *num.Uint {
    70  	bestAsk, err := orderBook.GetBestAskPrice()
    71  	// no best ask
    72  	if err != nil {
    73  		return nil
    74  	}
    75  	bestBid, err := orderBook.GetBestBidPrice()
    76  	// no best bid
    77  	if err != nil {
    78  		return nil
    79  	}
    80  
    81  	vBuy := uint64(C.ToDecimal().Div(initialScalingFactor.Mul(slippageFactor.Add(shortRiskFactor))).Div(bestBid.ToDecimal()).IntPart())
    82  	vwapBuy, err := orderBook.VWAP(vBuy, types.SideBuy)
    83  	// insufficient quantity in the book for vbuy quantity
    84  	if err != nil {
    85  		return nil
    86  	}
    87  
    88  	vSell := uint64(C.ToDecimal().Div(initialScalingFactor.Mul(slippageFactor.Add(longRiskFactor))).Div(bestAsk.ToDecimal()).IntPart())
    89  	vwapSell, err := orderBook.VWAP(vSell, types.SideSell)
    90  	// insufficient quantity in the book for vsell quantity
    91  	if err != nil {
    92  		return nil
    93  	}
    94  
    95  	return num.UintZero().Div(vwapSell.AddSum(vwapBuy), num.NewUint(2))
    96  }
    97  
    98  // MedianPrice returns the median of the given prices (pBook, pTrades, pOracle1..n).
    99  func MedianPrice(prices []*num.Uint) *num.Uint {
   100  	if prices == nil {
   101  		return nil
   102  	}
   103  
   104  	return num.Median(prices)
   105  }
   106  
   107  // CompositePriceByMedian returns the median mark price out of the non stale ones or nil if there is none.
   108  func CompositePriceByMedian(prices []*num.Uint, lastUpdate []int64, delta []time.Duration, t int64) *num.Uint {
   109  	pricesToConsider := []*num.Uint{}
   110  	for i, u := range prices {
   111  		if t-lastUpdate[i] <= delta[i].Nanoseconds() && u != nil && !u.IsZero() {
   112  			pricesToConsider = append(pricesToConsider, u)
   113  		}
   114  	}
   115  	if len(pricesToConsider) == 0 {
   116  		return nil
   117  	}
   118  	return num.Median(pricesToConsider)
   119  }
   120  
   121  // CompositePriceByWeight calculates the mid price out of the non-stale price by the weights assigned to each mid price.
   122  func CompositePriceByWeight(prices []*num.Uint, weights []num.Decimal, lastUpdateTime []int64, delta []time.Duration, t int64) *num.Uint {
   123  	pricesToConsider := []*num.Uint{}
   124  	priceWeights := []num.Decimal{}
   125  	weightSum := num.DecimalZero()
   126  	for i, u := range prices {
   127  		if t-lastUpdateTime[i] <= delta[i].Nanoseconds() && u != nil && !u.IsZero() {
   128  			pricesToConsider = append(pricesToConsider, u)
   129  			priceWeights = append(priceWeights, weights[i])
   130  			weightSum = weightSum.Add(weights[i])
   131  		}
   132  	}
   133  	if len(pricesToConsider) == 0 || weightSum.IsZero() {
   134  		return nil
   135  	}
   136  	price := num.UintZero()
   137  	for i := 0; i < len(pricesToConsider); i++ {
   138  		mp, _ := num.UintFromDecimal(pricesToConsider[i].ToDecimal().Mul(priceWeights[i]).Div(weightSum))
   139  		price.AddSum(mp)
   140  	}
   141  	return price
   142  }
   143  
   144  // CalculateTimeWeightedAverageBookPrice calculates the time weighted average of the timepoints where book price
   145  // was calculated.
   146  func CalculateTimeWeightedAverageBookPrice(timeToPrice map[int64]*num.Uint, t int64, markPricePeriod int64) *num.Uint {
   147  	if len(timeToPrice) == 0 {
   148  		return nil
   149  	}
   150  
   151  	keys := make([]int64, 0, len(timeToPrice))
   152  	for k := range timeToPrice {
   153  		if k >= t-markPricePeriod {
   154  			keys = append(keys, k)
   155  		}
   156  	}
   157  	sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
   158  	if len(keys) == 0 {
   159  		return nil
   160  	}
   161  	totalDuration := num.DecimalFromInt64(t - keys[0])
   162  	mp := num.DecimalZero()
   163  	for i, timepoint := range keys {
   164  		var duration int64
   165  		if i == len(keys)-1 {
   166  			duration = t - timepoint
   167  		} else {
   168  			duration = keys[i+1] - timepoint
   169  		}
   170  		var timeWeight num.Decimal
   171  		if totalDuration.IsZero() {
   172  			if len(keys) == 1 {
   173  				// if there's just one observation it should get all the weight
   174  				timeWeight = num.DecimalOne()
   175  			} else {
   176  				timeWeight = num.DecimalZero()
   177  			}
   178  		} else {
   179  			timeWeight = num.DecimalFromInt64(duration).Div(totalDuration)
   180  		}
   181  
   182  		mp = mp.Add(timeWeight.Mul(timeToPrice[timepoint].ToDecimal()))
   183  	}
   184  	mpAsU, _ := num.UintFromDecimal(mp)
   185  	return mpAsU
   186  }