code.vegaprotocol.io/vega@v0.79.0/core/execution/common/mark_price.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 "context" 20 "fmt" 21 "sort" 22 "time" 23 24 dscommon "code.vegaprotocol.io/vega/core/datasource/common" 25 "code.vegaprotocol.io/vega/core/matching" 26 "code.vegaprotocol.io/vega/core/products" 27 "code.vegaprotocol.io/vega/core/types" 28 "code.vegaprotocol.io/vega/libs/num" 29 "code.vegaprotocol.io/vega/protos/vega" 30 snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 31 ) 32 33 type CompositePriceCalculator struct { 34 config *types.CompositePriceConfiguration 35 trades []*types.Trade 36 sourceLastUpdate []int64 37 bookPriceAtTime map[int64]*num.Uint 38 price *num.Uint 39 timeService TimeService 40 // [0] trade mark price 41 // [1] book mark price 42 // [2] first oracel mark price 43 // [2+n] median mark price 44 priceSources []*num.Uint 45 oracles []*products.CompositePriceOracle 46 scalingFunc func(context.Context, *num.Numeric, int64) *num.Uint 47 maxPrice *num.Uint 48 49 dataPointListener func(context.Context, string, *num.Uint) // pass through data-points 50 } 51 52 const ( 53 TradePriceIndex = 0 54 BookPriceIndex = 1 55 FirstOraclePriceIndex = 2 56 ) 57 58 func NewCompositePriceCalculatorFromSnapshot(ctx context.Context, mp *num.Uint, timeService TimeService, oe OracleEngine, mpc *snapshot.CompositePriceCalculator) *CompositePriceCalculator { 59 if mpc == nil { 60 // migration - for existing markets loaded from snapshot, set the configuration to default to use last trade price 61 // for mark price 62 return &CompositePriceCalculator{ 63 config: &types.CompositePriceConfiguration{ 64 DecayWeight: num.DecimalZero(), 65 DecayPower: num.DecimalZero(), 66 CashAmount: num.UintZero(), 67 CompositePriceType: types.CompositePriceTypeByLastTrade, 68 }, 69 trades: []*types.Trade{}, 70 price: mp, 71 priceSources: make([]*num.Uint, 1), 72 sourceLastUpdate: make([]int64, 1), 73 timeService: timeService, 74 } 75 } 76 77 config := types.CompositePriceConfigurationFromProto(mpc.PriceConfiguration) 78 trades := make([]*types.Trade, 0, len(mpc.Trades)) 79 for _, t := range mpc.Trades { 80 trades = append(trades, types.TradeFromProto(t)) 81 } 82 priceSources := make([]*num.Uint, 0, len(mpc.PriceSources)) 83 for _, v := range mpc.PriceSources { 84 if len(v) == 0 { 85 priceSources = append(priceSources, nil) 86 } else { 87 priceSources = append(priceSources, num.MustUintFromString(v, 10)) 88 } 89 } 90 var compositePrice *num.Uint 91 if len(mpc.CompositePrice) > 0 { 92 compositePrice = num.MustUintFromString(mpc.CompositePrice, 10) 93 } 94 95 bookPriceAtTime := make(map[int64]*num.Uint, len(mpc.BookPriceAtTime)) 96 for _, tp := range mpc.BookPriceAtTime { 97 bookPriceAtTime[tp.Time] = num.MustUintFromString(tp.Price, 10) 98 } 99 100 calc := &CompositePriceCalculator{ 101 config: config, 102 trades: trades, 103 sourceLastUpdate: mpc.PriceSourceLastUpdate, 104 priceSources: priceSources, 105 bookPriceAtTime: bookPriceAtTime, 106 price: compositePrice, 107 timeService: timeService, 108 } 109 110 if len(config.DataSources) > 0 { 111 oracles := make([]*products.CompositePriceOracle, 0, len(config.DataSources)) 112 for i, s := range config.DataSources { 113 oracle, err := products.NewCompositePriceOracle(ctx, oe, s, config.SpecBindingForCompositePrice[i], calc.GetUpdateOraclePriceFunc(i)) 114 if err != nil { 115 return nil 116 } 117 oracles = append(oracles, oracle) 118 } 119 calc.oracles = oracles 120 } 121 return calc 122 } 123 124 func NewCompositePriceCalculator(ctx context.Context, config *types.CompositePriceConfiguration, oe products.OracleEngine, timeService TimeService) *CompositePriceCalculator { 125 priceSourcesLen := len(config.SourceStalenessTolerance) 126 if priceSourcesLen == 0 { 127 priceSourcesLen = 1 128 } 129 130 mpc := &CompositePriceCalculator{ 131 config: config, 132 priceSources: make([]*num.Uint, priceSourcesLen), 133 sourceLastUpdate: make([]int64, priceSourcesLen), 134 bookPriceAtTime: map[int64]*num.Uint{}, 135 timeService: timeService, 136 } 137 if len(config.DataSources) > 0 { 138 oracles := make([]*products.CompositePriceOracle, 0, len(config.DataSources)) 139 for i, s := range config.DataSources { 140 oracle, err := products.NewCompositePriceOracle(ctx, oe, s, config.SpecBindingForCompositePrice[i], mpc.GetUpdateOraclePriceFunc(i)) 141 if err != nil { 142 return nil 143 } 144 oracles = append(oracles, oracle) 145 } 146 mpc.oracles = oracles 147 } 148 return mpc 149 } 150 151 func (mpc *CompositePriceCalculator) NotifyOnDataSourcePropagation(listener func(context.Context, string, *num.Uint)) { 152 mpc.dataPointListener = listener 153 } 154 155 func (mpc *CompositePriceCalculator) UpdateConfig(ctx context.Context, oe OracleEngine, config *types.CompositePriceConfiguration) error { 156 // special case for only resetting the oracles 157 if mpc.oracles != nil { 158 for _, cpo := range mpc.oracles { 159 cpo.UnsubAll(ctx) 160 } 161 mpc.oracles = nil 162 } 163 164 if config == nil { 165 return nil 166 } 167 168 priceSourcesLen := len(config.SourceStalenessTolerance) 169 if priceSourcesLen == 0 { 170 priceSourcesLen = 1 171 } 172 mpc.config = config 173 mpc.priceSources = make([]*num.Uint, priceSourcesLen) 174 mpc.sourceLastUpdate = make([]int64, priceSourcesLen) 175 if mpc.bookPriceAtTime == nil { 176 mpc.bookPriceAtTime = map[int64]*num.Uint{} 177 } 178 179 if len(config.DataSources) > 0 { 180 oracles := make([]*products.CompositePriceOracle, 0, len(config.DataSources)) 181 for i, s := range config.DataSources { 182 oracle, err := products.NewCompositePriceOracle(ctx, oe, s, config.SpecBindingForCompositePrice[i], mpc.GetUpdateOraclePriceFunc(i)) 183 if err != nil { 184 return err 185 } 186 oracles = append(oracles, oracle) 187 } 188 mpc.oracles = oracles 189 } 190 return nil 191 } 192 193 func (mpc *CompositePriceCalculator) Close(ctx context.Context) { 194 if mpc.oracles != nil { 195 for _, cpo := range mpc.oracles { 196 cpo.UnsubAll(ctx) 197 } 198 } 199 } 200 201 func (mpc *CompositePriceCalculator) SetOraclePriceScalingFunc(f func(context.Context, *num.Numeric, int64) *num.Uint) { 202 mpc.scalingFunc = f 203 } 204 205 // OverridePrice is called to set the price externally. This is used when leaving the opening auction if the 206 // methodology yielded no valid price. 207 func (mpc *CompositePriceCalculator) OverridePrice(p *num.Uint) { 208 if p != nil { 209 mpc.price = p.Clone() 210 } 211 } 212 213 // SetMaxPriceCap is called from a capped future, to filter out price data that exceeds the max price. 214 func (mpc *CompositePriceCalculator) SetMaxPriceCap(mp *num.Uint) { 215 mpc.maxPrice = mp 216 } 217 218 // NewTrade is called to inform the mark price calculator on a new trade. 219 // All the trades for a given mark price calculation interval are saved until the end of the interval. 220 func (mpc *CompositePriceCalculator) NewTrade(trade *types.Trade) { 221 if trade.Seller == "network" || trade.Buyer == "network" { 222 return 223 } 224 mpc.trades = append(mpc.trades, trade) 225 mpc.sourceLastUpdate[TradePriceIndex] = trade.Timestamp 226 } 227 228 // UpdateOraclePrice is called when a new oracle price is available. 229 func (mpc *CompositePriceCalculator) GetUpdateOraclePriceFunc(oracleIndex int) func(ctx context.Context, data dscommon.Data) error { 230 return func(ctx context.Context, data dscommon.Data) error { 231 oracle := mpc.oracles[oracleIndex] 232 pd, err := oracle.GetData(data) 233 if err != nil { 234 return err 235 } 236 237 p := mpc.scalingFunc(ctx, pd, mpc.oracles[oracleIndex].GetDecimals()) 238 // ignore prices that exceed the cap 239 if p == nil || p.IsZero() { 240 return nil 241 } 242 243 // ignore price data > max price 244 if mpc.maxPrice != nil && p.GT(mpc.maxPrice) { 245 return nil 246 } 247 248 // propagate oracle price further along the chain 249 mpc.dataPointListener(ctx, mpc.config.DataSources[oracleIndex].ID, p) 250 251 mpc.priceSources[FirstOraclePriceIndex+oracleIndex] = p.Clone() 252 mpc.sourceLastUpdate[FirstOraclePriceIndex+oracleIndex] = mpc.timeService.GetTimeNow().UnixNano() 253 return nil 254 } 255 } 256 257 // CalculateBookMarkPriceAtTimeT is called every interval (currently at the end of each block) to calculate 258 // the mark price implied by the book. 259 // If there is insufficient quantity in the book, ignore this price 260 // IF the market is in auction set the mark price to the indicative price if not zero. 261 func (mpc *CompositePriceCalculator) CalculateBookMarkPriceAtTimeT(initialScalingFactor, slippageFactor, shortRiskFactor, longRiskFactor num.Decimal, t int64, ob *matching.CachedOrderBook) { 262 if mpc.config.CompositePriceType == types.CompositePriceTypeByLastTrade { 263 return 264 } 265 if ob.InAuction() { 266 indicative := ob.GetIndicativePrice() 267 if !indicative.IsZero() { 268 mpc.bookPriceAtTime[t] = indicative 269 mpc.sourceLastUpdate[BookPriceIndex] = t 270 } 271 return 272 } 273 mp := PriceFromBookAtTime(mpc.config.CashAmount, initialScalingFactor, slippageFactor, shortRiskFactor, longRiskFactor, ob) 274 if mp != nil { 275 mpc.bookPriceAtTime[t] = mp 276 mpc.sourceLastUpdate[BookPriceIndex] = t 277 } 278 } 279 280 func (mpc *CompositePriceCalculator) SetBookPriceAtTimeT(mp *num.Uint, t int64) { 281 if mpc.config.CompositePriceType == types.CompositePriceTypeByLastTrade { 282 return 283 } 284 if mp != nil && !mp.IsZero() { 285 mpc.bookPriceAtTime[t] = mp 286 mpc.sourceLastUpdate[BookPriceIndex] = t 287 } 288 } 289 290 func (mpc *CompositePriceCalculator) GetPrice() *num.Uint { 291 if mpc.price != nil { 292 return mpc.price.Clone() 293 } 294 return mpc.price 295 } 296 297 func (mpc *CompositePriceCalculator) GetConfig() *types.CompositePriceConfiguration { 298 return mpc.config 299 } 300 301 func (mpc *CompositePriceCalculator) updateMarkPriceIfNotInAuction(ctx context.Context, checkPriceMonitor bool, priceMonitor PriceMonitor, as AuctionState, mpcCandidate *num.Uint, resetPriceMonitoringEngine bool) error { 302 if !checkPriceMonitor { 303 mpc.price = mpcCandidate 304 return nil 305 } 306 if resetPriceMonitoringEngine { 307 priceMonitor.ResetPriceHistory(mpcCandidate) 308 } else { 309 priceMonitor.CheckPrice(ctx, as, mpcCandidate, true, true) 310 } 311 if as.InAuction() || as.AuctionStart() { 312 return fmt.Errorf("price monitoring failed for the new mark price") 313 } 314 mpc.price = mpcCandidate 315 return nil 316 } 317 318 // CalculateMarkPrice is called at the end of each mark price calculation interval and calculates the mark price 319 // using the mark price type methodology. 320 func (mpc *CompositePriceCalculator) CalculateMarkPrice(ctx context.Context, priceMonitor PriceMonitor, as AuctionState, t int64, ob *matching.CachedOrderBook, markPriceFrequency time.Duration, initialScalingFactor, slippageFactor, shortRiskFactor, longRiskFactor num.Decimal, checkPriceMonitor bool, resetPriceMonitoringEngine bool) (*num.Uint, error) { 321 var err error 322 if mpc.config.CompositePriceType == types.CompositePriceTypeByLastTrade { 323 // if there are no trades, the mark price remains what it was before. 324 if len(mpc.trades) > 0 { 325 mpcCandidate := mpc.trades[len(mpc.trades)-1].Price.Clone() 326 err = mpc.updateMarkPriceIfNotInAuction(ctx, checkPriceMonitor, priceMonitor, as, mpcCandidate, resetPriceMonitoringEngine) 327 } 328 mpc.trades = []*types.Trade{} 329 return mpc.price, err 330 } 331 if len(mpc.trades) > 0 { 332 if pft := PriceFromTrades(mpc.trades, mpc.config.DecayWeight, num.DecimalFromInt64(markPriceFrequency.Nanoseconds()), mpc.config.DecayPower, t); pft != nil && !pft.IsZero() { 333 mpc.priceSources[TradePriceIndex] = pft 334 } 335 } 336 if p := CalculateTimeWeightedAverageBookPrice(mpc.bookPriceAtTime, t, markPriceFrequency.Nanoseconds()); p != nil { 337 mpc.priceSources[BookPriceIndex] = p 338 } 339 340 if p := CompositePriceByMedian(mpc.priceSources[:len(mpc.priceSources)-1], mpc.sourceLastUpdate[:len(mpc.priceSources)-1], mpc.config.SourceStalenessTolerance[:len(mpc.priceSources)-1], t); p != nil && !p.IsZero() { 341 mpc.priceSources[len(mpc.priceSources)-1] = p 342 latest := int64(-1) 343 for _, v := range mpc.sourceLastUpdate[:len(mpc.priceSources)-1] { 344 if v > latest { 345 latest = v 346 } 347 } 348 if latest > mpc.sourceLastUpdate[len(mpc.priceSources)-1] { 349 mpc.sourceLastUpdate[len(mpc.priceSources)-1] = latest 350 } 351 } 352 if mpc.config.CompositePriceType == types.CompositePriceTypeByMedian { 353 if p := CompositePriceByMedian(mpc.priceSources, mpc.sourceLastUpdate, mpc.config.SourceStalenessTolerance, t); p != nil && !p.IsZero() { 354 err = mpc.updateMarkPriceIfNotInAuction(ctx, checkPriceMonitor, priceMonitor, as, p, resetPriceMonitoringEngine) 355 } 356 } else { 357 if p := CompositePriceByWeight(mpc.priceSources, mpc.config.SourceWeights, mpc.sourceLastUpdate, mpc.config.SourceStalenessTolerance, t); p != nil && !p.IsZero() { 358 err = mpc.updateMarkPriceIfNotInAuction(ctx, checkPriceMonitor, priceMonitor, as, p, resetPriceMonitoringEngine) 359 } 360 } 361 mpc.trades = []*types.Trade{} 362 mpc.bookPriceAtTime = map[int64]*num.Uint{} 363 mpc.CalculateBookMarkPriceAtTimeT(initialScalingFactor, slippageFactor, shortRiskFactor, longRiskFactor, t, ob) 364 return mpc.price, err 365 } 366 367 func (mpc *CompositePriceCalculator) IntoProto() *snapshot.CompositePriceCalculator { 368 var compositePrice string 369 if mpc.price != nil { 370 compositePrice = mpc.price.String() 371 } 372 373 priceSources := make([]string, 0, len(mpc.priceSources)) 374 for _, u := range mpc.priceSources { 375 if u == nil { 376 priceSources = append(priceSources, "") 377 } else { 378 priceSources = append(priceSources, u.String()) 379 } 380 } 381 trades := make([]*vega.Trade, 0, len(mpc.trades)) 382 for _, t := range mpc.trades { 383 trades = append(trades, t.IntoProto()) 384 } 385 bookPriceAtTime := make([]*snapshot.TimePrice, 0, len(mpc.bookPriceAtTime)) 386 for k, u := range mpc.bookPriceAtTime { 387 var p string 388 if u != nil { 389 p = u.String() 390 } 391 bookPriceAtTime = append(bookPriceAtTime, &snapshot.TimePrice{Time: k, Price: p}) 392 } 393 sort.Slice(bookPriceAtTime, func(i, j int) bool { 394 return bookPriceAtTime[i].Time < bookPriceAtTime[j].Time 395 }) 396 397 return &snapshot.CompositePriceCalculator{ 398 CompositePrice: compositePrice, 399 PriceConfiguration: mpc.config.IntoProto(), 400 PriceSources: priceSources, 401 Trades: trades, 402 PriceSourceLastUpdate: mpc.sourceLastUpdate, 403 BookPriceAtTime: bookPriceAtTime, 404 } 405 } 406 407 func (mpc *CompositePriceCalculator) GetData() *types.CompositePriceState { 408 priceSources := make([]*types.CompositePriceSource, 0, len(mpc.priceSources)) 409 410 for i, ps := range mpc.priceSources { 411 if ps != nil { 412 var priceSourceName string 413 if i == TradePriceIndex { 414 priceSourceName = "priceFromTrades" 415 } else if i == BookPriceIndex { 416 priceSourceName = "priceFromOrderBook" 417 } else if i == len(mpc.priceSources)-1 { 418 priceSourceName = "medianPrice" 419 } else { 420 priceSourceName = fmt.Sprintf("priceFromOracle%d", i-FirstOraclePriceIndex+1) 421 } 422 priceSources = append(priceSources, &types.CompositePriceSource{ 423 PriceSource: priceSourceName, 424 Price: ps, 425 LastUpdated: mpc.sourceLastUpdate[i], 426 }) 427 } 428 } 429 430 return &types.CompositePriceState{PriceSources: priceSources} 431 }