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 }