code.vegaprotocol.io/vega@v0.79.0/datanode/entities/marketdata.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 entities
    17  
    18  import (
    19  	"bytes"
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2"
    27  	"code.vegaprotocol.io/vega/protos/vega"
    28  	types "code.vegaprotocol.io/vega/protos/vega"
    29  
    30  	"github.com/shopspring/decimal"
    31  )
    32  
    33  var ErrMarketDataIntegerOverflow = errors.New("integer overflow encountered when converting market data for persistence")
    34  
    35  // MarketData represents a market data record that is stored in the SQL database.
    36  type MarketData struct {
    37  	// Mark price, as an integer, for example `123456` is a correctly
    38  	// formatted price of `1.23456` assuming market configured to 5 decimal places
    39  	MarkPrice decimal.Decimal
    40  	// Highest price level on an order book for buy orders, as an integer, for example `123456` is a correctly
    41  	// formatted price of `1.23456` assuming market configured to 5 decimal places
    42  	BestBidPrice decimal.Decimal
    43  	// Aggregated volume being bid at the best bid price
    44  	BestBidVolume uint64
    45  	// Aggregated volume being bid at the best bid price
    46  	BestOfferPrice decimal.Decimal
    47  	// Aggregated volume being offered at the best offer price, as an integer, for example `123456` is a correctly
    48  	// formatted price of `1.23456` assuming market configured to 5 decimal places
    49  	BestOfferVolume uint64
    50  	// Highest price on the order book for buy orders not including pegged orders
    51  	BestStaticBidPrice decimal.Decimal
    52  	// Total volume at the best static bid price excluding pegged orders
    53  	BestStaticBidVolume uint64
    54  	// Lowest price on the order book for sell orders not including pegged orders
    55  	BestStaticOfferPrice decimal.Decimal
    56  	// Total volume at the best static offer price excluding pegged orders
    57  	BestStaticOfferVolume uint64
    58  	// Arithmetic average of the best bid price and best offer price, as an integer, for example `123456` is a correctly
    59  	// formatted price of `1.23456` assuming market configured to 5 decimal places
    60  	MidPrice decimal.Decimal
    61  	// Arithmetic average of the best static bid price and best static offer price
    62  	StaticMidPrice decimal.Decimal
    63  	// Market identifier for the data
    64  	Market MarketID
    65  	// The sum of the size of all positions greater than 0 on the market
    66  	OpenInterest uint64
    67  	// Time in seconds until the end of the auction (0 if currently not in auction period)
    68  	AuctionEnd int64
    69  	// Time until next auction (used in FBA's) - currently always 0
    70  	AuctionStart int64
    71  	// Indicative price (zero if not in auction)
    72  	IndicativePrice decimal.Decimal
    73  	// Indicative volume (zero if not in auction)
    74  	IndicativeVolume uint64
    75  	// The current trading mode for the market
    76  	MarketTradingMode string
    77  	// The current trading mode for the market
    78  	MarketState string
    79  	// When a market is in an auction trading mode, this field indicates what triggered the auction
    80  	AuctionTrigger string
    81  	// When a market auction is extended, this field indicates what caused the extension
    82  	ExtensionTrigger string
    83  	// Targeted stake for the given market
    84  	TargetStake num.Decimal
    85  	// Available stake for the given market
    86  	SuppliedStake num.Decimal
    87  	// One or more price monitoring bounds for the current timestamp
    88  	PriceMonitoringBounds []*types.PriceMonitoringBounds
    89  	// the market value proxy
    90  	MarketValueProxy string
    91  	// the equity like share of liquidity fee for each liquidity provider
    92  	LiquidityProviderFeeShares []*types.LiquidityProviderFeeShare
    93  	// the SLA statistics for each liquidity provider
    94  	LiquidityProviderSLA []*types.LiquidityProviderSLA
    95  	// A synthetic time created which is the sum of vega_time + (seq num * Microsecond)
    96  	SyntheticTime time.Time
    97  	// Transaction which caused this update
    98  	TxHash TxHash
    99  	// Vega Block time at which the data was received from Vega Node
   100  	VegaTime time.Time
   101  	// SeqNum is the order in which the market data was received in the block
   102  	SeqNum uint64
   103  	// NextMarkToMarket is the next timestamp when the market will be marked to market
   104  	NextMarkToMarket time.Time
   105  	// market growth for the last market window
   106  	MarketGrowth num.Decimal
   107  	// Last traded price, as an integer, for example `123456` is a correctly
   108  	// formatted price of `1.23456` assuming market configured to 5 decimal places
   109  	LastTradedPrice num.Decimal
   110  	// Data specific to the product type
   111  	ProductData *ProductData
   112  	// NextNetworkCloseout is the time at which the network will attempt its next closeout order.
   113  	NextNetworkCloseout time.Time
   114  	// The methodology used for the calculation of the mark price.
   115  	MarkPriceType string
   116  	// The internal state of the mark price composite price.
   117  	MarkPriceState *CompositePriceState
   118  	// The state of the active protocol
   119  	ActiveProtocolAutomatedPurchase *vega.ProtocolAutomatedPurchaseData
   120  }
   121  
   122  type PriceMonitoringTrigger struct {
   123  	Horizon          uint64          `json:"horizon"`
   124  	Probability      decimal.Decimal `json:"probability"`
   125  	AuctionExtension uint64          `json:"auctionExtension"`
   126  }
   127  
   128  func (trigger PriceMonitoringTrigger) Equals(other PriceMonitoringTrigger) bool {
   129  	return trigger.Horizon == other.Horizon &&
   130  		trigger.Probability.Equal(other.Probability) &&
   131  		trigger.AuctionExtension == other.AuctionExtension
   132  }
   133  
   134  func (trigger PriceMonitoringTrigger) ToProto() *types.PriceMonitoringTrigger {
   135  	return &types.PriceMonitoringTrigger{
   136  		Horizon:          int64(trigger.Horizon),
   137  		Probability:      trigger.Probability.String(),
   138  		AuctionExtension: int64(trigger.AuctionExtension),
   139  	}
   140  }
   141  
   142  func MarketDataFromProto(data *types.MarketData, txHash TxHash) (*MarketData, error) {
   143  	var mark, bid, offer, staticBid, staticOffer, mid, staticMid, indicative, targetStake, suppliedStake, growth, lastTradedPrice num.Decimal
   144  	var err error
   145  
   146  	if mark, err = parseDecimal(data.MarkPrice); err != nil {
   147  		return nil, err
   148  	}
   149  	if lastTradedPrice, err = parseDecimal(data.LastTradedPrice); err != nil {
   150  		return nil, err
   151  	}
   152  	if bid, err = parseDecimal(data.BestBidPrice); err != nil {
   153  		return nil, err
   154  	}
   155  	if offer, err = parseDecimal(data.BestOfferPrice); err != nil {
   156  		return nil, err
   157  	}
   158  	if staticBid, err = parseDecimal(data.BestStaticBidPrice); err != nil {
   159  		return nil, err
   160  	}
   161  	if staticOffer, err = parseDecimal(data.BestStaticOfferPrice); err != nil {
   162  		return nil, err
   163  	}
   164  	if mid, err = parseDecimal(data.MidPrice); err != nil {
   165  		return nil, err
   166  	}
   167  	if staticMid, err = parseDecimal(data.StaticMidPrice); err != nil {
   168  		return nil, err
   169  	}
   170  	if indicative, err = parseDecimal(data.IndicativePrice); err != nil {
   171  		return nil, err
   172  	}
   173  	if targetStake, err = parseDecimal(data.TargetStake); err != nil {
   174  		return nil, err
   175  	}
   176  	if suppliedStake, err = parseDecimal(data.SuppliedStake); err != nil {
   177  		return nil, err
   178  	}
   179  	if growth, err = parseDecimal(data.MarketGrowth); err != nil {
   180  		return nil, err
   181  	}
   182  	nextMTM := time.Unix(0, data.NextMarkToMarket)
   183  	nextNC := time.Unix(0, data.NextNetworkCloseout)
   184  
   185  	marketData := &MarketData{
   186  		LastTradedPrice:                 lastTradedPrice,
   187  		MarkPrice:                       mark,
   188  		BestBidPrice:                    bid,
   189  		BestBidVolume:                   data.BestBidVolume,
   190  		BestOfferPrice:                  offer,
   191  		BestOfferVolume:                 data.BestOfferVolume,
   192  		BestStaticBidPrice:              staticBid,
   193  		BestStaticBidVolume:             data.BestStaticBidVolume,
   194  		BestStaticOfferPrice:            staticOffer,
   195  		BestStaticOfferVolume:           data.BestStaticOfferVolume,
   196  		MidPrice:                        mid,
   197  		StaticMidPrice:                  staticMid,
   198  		Market:                          MarketID(data.Market),
   199  		OpenInterest:                    data.OpenInterest,
   200  		AuctionEnd:                      data.AuctionEnd,
   201  		AuctionStart:                    data.AuctionStart,
   202  		IndicativePrice:                 indicative,
   203  		IndicativeVolume:                data.IndicativeVolume,
   204  		MarketState:                     data.MarketState.String(),
   205  		MarketTradingMode:               data.MarketTradingMode.String(),
   206  		AuctionTrigger:                  data.Trigger.String(),
   207  		ExtensionTrigger:                data.ExtensionTrigger.String(),
   208  		TargetStake:                     targetStake,
   209  		SuppliedStake:                   suppliedStake,
   210  		PriceMonitoringBounds:           data.PriceMonitoringBounds,
   211  		MarketValueProxy:                data.MarketValueProxy,
   212  		LiquidityProviderFeeShares:      data.LiquidityProviderFeeShare,
   213  		LiquidityProviderSLA:            data.LiquidityProviderSla,
   214  		TxHash:                          txHash,
   215  		NextMarkToMarket:                nextMTM,
   216  		MarketGrowth:                    growth,
   217  		NextNetworkCloseout:             nextNC,
   218  		MarkPriceType:                   data.MarkPriceType.String(),
   219  		ActiveProtocolAutomatedPurchase: data.ActiveProtocolAutomatedPurchase,
   220  	}
   221  
   222  	if data.MarkPriceState != nil {
   223  		marketData.MarkPriceState = &CompositePriceState{data.MarkPriceState}
   224  	}
   225  
   226  	if data.ProductData != nil {
   227  		marketData.ProductData = &ProductData{data.ProductData}
   228  	}
   229  
   230  	return marketData, nil
   231  }
   232  
   233  func parseDecimal(input string) (decimal.Decimal, error) {
   234  	if input == "" {
   235  		return decimal.Zero, nil
   236  	}
   237  
   238  	v, err := decimal.NewFromString(input)
   239  	if err != nil {
   240  		return decimal.Zero, err
   241  	}
   242  
   243  	return v, nil
   244  }
   245  
   246  func (md MarketData) Equal(other MarketData) bool {
   247  	productData1 := []byte{}
   248  	productData2 := []byte{}
   249  	if md.ProductData != nil {
   250  		productData1, _ = md.ProductData.MarshalJSON()
   251  	}
   252  	if other.ProductData != nil {
   253  		productData2, _ = other.ProductData.MarshalJSON()
   254  	}
   255  
   256  	markPriceState1 := []byte{}
   257  	markPriceState2 := []byte{}
   258  	if md.MarkPriceState != nil {
   259  		markPriceState1, _ = md.MarkPriceState.MarshalJSON()
   260  	}
   261  	if other.MarkPriceState != nil {
   262  		markPriceState2, _ = other.MarkPriceState.MarshalJSON()
   263  	}
   264  
   265  	papStateIsEqual := (md.ActiveProtocolAutomatedPurchase == nil) == (other.ActiveProtocolAutomatedPurchase == nil) &&
   266  		(md.ActiveProtocolAutomatedPurchase == nil || md.ActiveProtocolAutomatedPurchase.Id == other.ActiveProtocolAutomatedPurchase.Id && md.ActiveProtocolAutomatedPurchase.OrderId == other.ActiveProtocolAutomatedPurchase.OrderId)
   267  
   268  	return md.LastTradedPrice.Equals(other.LastTradedPrice) &&
   269  		md.MarkPrice.Equals(other.MarkPrice) &&
   270  		md.BestBidPrice.Equals(other.BestBidPrice) &&
   271  		md.BestOfferPrice.Equals(other.BestOfferPrice) &&
   272  		md.BestStaticBidPrice.Equals(other.BestStaticBidPrice) &&
   273  		md.BestStaticOfferPrice.Equals(other.BestStaticOfferPrice) &&
   274  		md.MidPrice.Equals(other.MidPrice) &&
   275  		md.StaticMidPrice.Equals(other.StaticMidPrice) &&
   276  		md.IndicativePrice.Equals(other.IndicativePrice) &&
   277  		md.TargetStake.Equals(other.TargetStake) &&
   278  		md.SuppliedStake.Equals(other.SuppliedStake) &&
   279  		md.BestBidVolume == other.BestBidVolume &&
   280  		md.BestOfferVolume == other.BestOfferVolume &&
   281  		md.BestStaticBidVolume == other.BestStaticBidVolume &&
   282  		md.BestStaticOfferVolume == other.BestStaticOfferVolume &&
   283  		md.OpenInterest == other.OpenInterest &&
   284  		md.AuctionEnd == other.AuctionEnd &&
   285  		md.AuctionStart == other.AuctionStart &&
   286  		md.IndicativeVolume == other.IndicativeVolume &&
   287  		md.Market == other.Market &&
   288  		md.MarketTradingMode == other.MarketTradingMode &&
   289  		md.AuctionTrigger == other.AuctionTrigger &&
   290  		md.ExtensionTrigger == other.ExtensionTrigger &&
   291  		md.MarketValueProxy == other.MarketValueProxy &&
   292  		priceMonitoringBoundsMatches(md.PriceMonitoringBounds, other.PriceMonitoringBounds) &&
   293  		liquidityProviderFeeShareMatches(md.LiquidityProviderFeeShares, other.LiquidityProviderFeeShares) &&
   294  		liquidityProviderSLAMatches(md.LiquidityProviderSLA, other.LiquidityProviderSLA) &&
   295  		md.TxHash == other.TxHash &&
   296  		md.MarketState == other.MarketState &&
   297  		md.NextMarkToMarket.Equal(other.NextMarkToMarket) &&
   298  		md.MarketGrowth.Equal(other.MarketGrowth) &&
   299  		bytes.Equal(productData1, productData2) &&
   300  		md.NextNetworkCloseout.Equal(other.NextNetworkCloseout) &&
   301  		md.MarkPriceType == other.MarkPriceType &&
   302  		bytes.Equal(markPriceState1, markPriceState2) && papStateIsEqual
   303  }
   304  
   305  func priceMonitoringBoundsMatches(bounds, other []*types.PriceMonitoringBounds) bool {
   306  	if len(bounds) != len(other) {
   307  		return false
   308  	}
   309  
   310  	for i, bound := range bounds {
   311  		if bound.Trigger == nil && other[i].Trigger != nil || bound.Trigger != nil && other[i].Trigger == nil {
   312  			return false
   313  		}
   314  
   315  		if bound.MinValidPrice != other[i].MinValidPrice ||
   316  			bound.MaxValidPrice != other[i].MaxValidPrice ||
   317  			bound.ReferencePrice != other[i].ReferencePrice || bound.Active != other[i].Active {
   318  			return false
   319  		}
   320  
   321  		if bound.Trigger != nil && other[i].Trigger != nil {
   322  			if bound.Trigger.Probability != other[i].Trigger.Probability ||
   323  				bound.Trigger.Horizon != other[i].Trigger.Horizon ||
   324  				bound.Trigger.AuctionExtension != other[i].Trigger.AuctionExtension {
   325  				return false
   326  			}
   327  		}
   328  	}
   329  
   330  	return true
   331  }
   332  
   333  func liquidityProviderFeeShareMatches(feeShares, other []*types.LiquidityProviderFeeShare) bool {
   334  	if len(feeShares) != len(other) {
   335  		return false
   336  	}
   337  
   338  	for i, fee := range feeShares {
   339  		if fee.EquityLikeShare != other[i].EquityLikeShare ||
   340  			fee.AverageEntryValuation != other[i].AverageEntryValuation ||
   341  			fee.AverageScore != other[i].AverageScore ||
   342  			fee.Party != other[i].Party {
   343  			return false
   344  		}
   345  	}
   346  
   347  	return true
   348  }
   349  
   350  func liquidityProviderSLAMatches(slas, other []*types.LiquidityProviderSLA) bool {
   351  	if len(slas) != len(other) {
   352  		return false
   353  	}
   354  
   355  	for i, sla := range slas {
   356  		if sla.CurrentEpochFractionOfTimeOnBook != other[i].CurrentEpochFractionOfTimeOnBook ||
   357  			sla.LastEpochBondPenalty != other[i].LastEpochBondPenalty ||
   358  			sla.LastEpochFeePenalty != other[i].LastEpochFeePenalty ||
   359  			sla.LastEpochFractionOfTimeOnBook != other[i].LastEpochFractionOfTimeOnBook {
   360  			return false
   361  		}
   362  	}
   363  
   364  	return true
   365  }
   366  
   367  func (md MarketData) ToProto() *types.MarketData {
   368  	result := types.MarketData{
   369  		LastTradedPrice:                 md.LastTradedPrice.String(),
   370  		MarkPrice:                       md.MarkPrice.String(),
   371  		BestBidPrice:                    md.BestBidPrice.String(),
   372  		BestBidVolume:                   md.BestBidVolume,
   373  		BestOfferPrice:                  md.BestOfferPrice.String(),
   374  		BestOfferVolume:                 md.BestOfferVolume,
   375  		BestStaticBidPrice:              md.BestStaticBidPrice.String(),
   376  		BestStaticBidVolume:             md.BestStaticBidVolume,
   377  		BestStaticOfferPrice:            md.BestStaticOfferPrice.String(),
   378  		BestStaticOfferVolume:           md.BestStaticOfferVolume,
   379  		MidPrice:                        md.MidPrice.String(),
   380  		StaticMidPrice:                  md.StaticMidPrice.String(),
   381  		Market:                          md.Market.String(),
   382  		Timestamp:                       md.VegaTime.UnixNano(),
   383  		OpenInterest:                    md.OpenInterest,
   384  		AuctionEnd:                      md.AuctionEnd,
   385  		AuctionStart:                    md.AuctionStart,
   386  		IndicativePrice:                 md.IndicativePrice.String(),
   387  		IndicativeVolume:                md.IndicativeVolume,
   388  		MarketState:                     types.Market_State(types.Market_State_value[md.MarketState]),
   389  		MarketTradingMode:               types.Market_TradingMode(types.Market_TradingMode_value[md.MarketTradingMode]),
   390  		Trigger:                         types.AuctionTrigger(types.AuctionTrigger_value[md.AuctionTrigger]),
   391  		ExtensionTrigger:                types.AuctionTrigger(types.AuctionTrigger_value[md.ExtensionTrigger]),
   392  		TargetStake:                     md.TargetStake.String(),
   393  		SuppliedStake:                   md.SuppliedStake.String(),
   394  		PriceMonitoringBounds:           md.PriceMonitoringBounds,
   395  		MarketValueProxy:                md.MarketValueProxy,
   396  		LiquidityProviderFeeShare:       md.LiquidityProviderFeeShares,
   397  		LiquidityProviderSla:            md.LiquidityProviderSLA,
   398  		NextMarkToMarket:                md.NextMarkToMarket.UnixNano(),
   399  		MarketGrowth:                    md.MarketGrowth.String(),
   400  		NextNetworkCloseout:             md.NextNetworkCloseout.UnixNano(),
   401  		MarkPriceType:                   types.CompositePriceType(types.CompositePriceType_value[md.MarkPriceType]),
   402  		ActiveProtocolAutomatedPurchase: md.ActiveProtocolAutomatedPurchase,
   403  	}
   404  
   405  	if md.ProductData != nil {
   406  		result.ProductData = md.ProductData.ProductData
   407  	}
   408  
   409  	if md.MarkPriceState != nil {
   410  		result.MarkPriceState = md.MarkPriceState.CompositePriceState
   411  	}
   412  
   413  	return &result
   414  }
   415  
   416  func (md MarketData) Cursor() *Cursor {
   417  	return NewCursor(MarketDataCursor{md.SyntheticTime}.String())
   418  }
   419  
   420  func (md MarketData) ToProtoEdge(_ ...any) (*v2.MarketDataEdge, error) {
   421  	return &v2.MarketDataEdge{
   422  		Node:   md.ToProto(),
   423  		Cursor: md.Cursor().Encode(),
   424  	}, nil
   425  }
   426  
   427  type MarketDataCursor struct {
   428  	SyntheticTime time.Time `json:"synthetic_time"`
   429  }
   430  
   431  func (c MarketDataCursor) String() string {
   432  	bs, err := json.Marshal(c)
   433  	if err != nil {
   434  		panic(fmt.Errorf("could not marshal market data cursor: %w", err))
   435  	}
   436  	return string(bs)
   437  }
   438  
   439  func (c *MarketDataCursor) Parse(cursorString string) error {
   440  	if cursorString == "" {
   441  		return nil
   442  	}
   443  
   444  	return json.Unmarshal([]byte(cursorString), c)
   445  }