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 }