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  }