code.vegaprotocol.io/vega@v0.79.0/core/execution/future/market_snapshot.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 future 17 18 import ( 19 "context" 20 "fmt" 21 "sort" 22 "time" 23 24 "code.vegaprotocol.io/vega/core/assets" 25 "code.vegaprotocol.io/vega/core/execution/amm" 26 "code.vegaprotocol.io/vega/core/execution/common" 27 "code.vegaprotocol.io/vega/core/execution/liquidation" 28 "code.vegaprotocol.io/vega/core/execution/stoporders" 29 "code.vegaprotocol.io/vega/core/fee" 30 "code.vegaprotocol.io/vega/core/liquidity/target" 31 "code.vegaprotocol.io/vega/core/liquidity/v2" 32 "code.vegaprotocol.io/vega/core/markets" 33 "code.vegaprotocol.io/vega/core/matching" 34 "code.vegaprotocol.io/vega/core/monitor" 35 "code.vegaprotocol.io/vega/core/monitor/price" 36 "code.vegaprotocol.io/vega/core/positions" 37 "code.vegaprotocol.io/vega/core/products" 38 "code.vegaprotocol.io/vega/core/risk" 39 "code.vegaprotocol.io/vega/core/settlement" 40 "code.vegaprotocol.io/vega/core/types" 41 vgcontext "code.vegaprotocol.io/vega/libs/context" 42 "code.vegaprotocol.io/vega/libs/num" 43 "code.vegaprotocol.io/vega/libs/ptr" 44 "code.vegaprotocol.io/vega/logging" 45 snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 46 47 "golang.org/x/exp/maps" 48 ) 49 50 func NewMarketFromSnapshot( 51 ctx context.Context, 52 log *logging.Logger, 53 em *types.ExecMarket, 54 riskConfig risk.Config, 55 positionConfig positions.Config, 56 settlementConfig settlement.Config, 57 matchingConfig matching.Config, 58 feeConfig fee.Config, 59 liquidityConfig liquidity.Config, 60 collateralEngine common.Collateral, 61 oracleEngine products.OracleEngine, 62 timeService common.TimeService, 63 broker common.Broker, 64 stateVarEngine common.StateVarEngine, 65 assetDetails *assets.Asset, 66 marketActivityTracker *common.MarketActivityTracker, 67 peggedOrderNotify func(int64), 68 referralDiscountRewardService fee.ReferralDiscountRewardService, 69 volumeDiscountService fee.VolumeDiscountService, 70 volumeRebateService fee.VolumeRebateService, 71 banking common.Banking, 72 parties common.Parties, 73 ) (*Market, error) { 74 mkt := em.Market 75 76 positionFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(mkt.PositionDecimalPlaces)) 77 if len(em.Market.ID) == 0 { 78 return nil, common.ErrEmptyMarketID 79 } 80 81 assetDecimals := assetDetails.DecimalPlaces() 82 83 tradableInstrument, err := markets.NewTradableInstrumentFromSnapshot(ctx, log, mkt.TradableInstrument, em.Market.ID, 84 timeService, oracleEngine, broker, em.Product, uint32(assetDecimals)) 85 if err != nil { 86 return nil, fmt.Errorf("unable to instantiate a new market: %w", err) 87 } 88 89 asset := tradableInstrument.Instrument.Product.GetAsset() 90 exp := int(assetDecimals) - int(mkt.DecimalPlaces) 91 priceFactor := num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(exp))) 92 assetFactor := num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(assetDecimals))) 93 94 as := monitor.NewAuctionStateFromSnapshot(mkt, em.AuctionState) 95 positionEngine := positions.NewSnapshotEngine(log, positionConfig, mkt.ID, broker) 96 97 var ammEngine *amm.Engine 98 if em.Amm == nil { 99 ammEngine = amm.New(log, broker, collateralEngine, mkt.GetID(), asset, positionEngine, priceFactor, positionFactor, marketActivityTracker, parties, mkt.AllowedEmptyAmmLevels) 100 } else { 101 ammEngine, err = amm.NewFromProto(log, broker, collateralEngine, mkt.GetID(), asset, positionEngine, em.Amm, priceFactor, positionFactor, marketActivityTracker, parties, mkt.AllowedEmptyAmmLevels) 102 if err != nil { 103 return nil, err 104 } 105 } 106 107 // @TODO -> the raw auctionstate shouldn't be something exposed to the matching engine 108 // as far as matching goes: it's either an auction or not 109 book := matching.NewCachedOrderBook( 110 log, matchingConfig, mkt.ID, as.InAuction(), peggedOrderNotify) 111 book.SetOffbookSource(ammEngine) 112 113 // this needs to stay 114 riskEngine := risk.NewEngine(log, 115 riskConfig, 116 tradableInstrument.MarginCalculator, 117 tradableInstrument.RiskModel, 118 book, 119 as, 120 timeService, 121 broker, 122 mkt.ID, 123 asset, 124 stateVarEngine, 125 positionFactor, 126 em.RiskFactorConsensusReached, 127 &types.RiskFactor{Market: mkt.ID, Short: em.ShortRiskFactor, Long: em.LongRiskFactor}, 128 mkt.LinearSlippageFactor, 129 mkt.QuadraticSlippageFactor, 130 ) 131 132 settleEngine := settlement.NewSnapshotEngine( 133 log, 134 settlementConfig, 135 tradableInstrument.Instrument.Product, 136 mkt.ID, 137 timeService, 138 broker, 139 positionFactor, 140 ) 141 142 var feeEngine *fee.Engine 143 if em.FeesStats != nil { 144 feeEngine, err = fee.NewFromState(log, feeConfig, *mkt.Fees, asset, positionFactor, em.FeesStats) 145 if err != nil { 146 return nil, fmt.Errorf("unable to instantiate fee engine: %w", err) 147 } 148 } else { 149 feeEngine, err = fee.New(log, feeConfig, *mkt.Fees, asset, positionFactor) 150 if err != nil { 151 return nil, fmt.Errorf("unable to instantiate fee engine: %w", err) 152 } 153 } 154 155 tsCalc := target.NewSnapshotEngine(*mkt.LiquidityMonitoringParameters.TargetStakeParameters, positionEngine, mkt.ID, positionFactor) 156 157 pMonitor, err := price.NewMonitorFromSnapshot(mkt.ID, asset, em.PriceMonitor, mkt.PriceMonitoringSettings, tradableInstrument.RiskModel, as, stateVarEngine, log) 158 if err != nil { 159 return nil, fmt.Errorf("unable to instantiate price monitoring engine: %w", err) 160 } 161 162 // TODO(jeremy): remove this once the upgrade with the .73 have run on mainnet 163 // this is required to support the migration to SLA liquidity 164 if !(mkt.LiquiditySLAParams != nil) { 165 mkt.LiquiditySLAParams = ptr.From(liquidity.DefaultSLAParameters) 166 } 167 168 liquidityEngine := liquidity.NewSnapshotEngine( 169 liquidityConfig, log, timeService, broker, tradableInstrument.RiskModel, 170 pMonitor, book, as, asset, mkt.ID, stateVarEngine, positionFactor, mkt.LiquiditySLAParams) 171 equityShares := common.NewEquitySharesFromSnapshot(em.EquityShare) 172 173 // if we're upgrading and the market liquidity state is nil, all we can do is take the old SLA values which will *probably* be the right ones 174 if vgcontext.InProgressUpgrade(ctx) && em.MarketLiquidity == nil { 175 em.MarketLiquidity = &snapshot.MarketLiquidity{ 176 PriceRange: mkt.LiquiditySLAParams.PriceRange.String(), 177 } 178 } 179 180 // just check for nil first just in case we are on a protocol upgrade from a version were AMM were not supported. 181 // @TODO pass in AMM 182 marketLiquidity, err := common.NewMarketLiquidityFromSnapshot( 183 log, liquidityEngine, collateralEngine, broker, book, equityShares, marketActivityTracker, 184 feeEngine, common.FutureMarketType, mkt.ID, asset, priceFactor, em.MarketLiquidity, ammEngine, 185 ) 186 if err != nil { 187 return nil, err 188 } 189 190 // backward compatibility check for nil 191 stopOrders := stoporders.New(log) 192 if em.StopOrders != nil { 193 stopOrders = stoporders.NewFromProto(log, em.StopOrders) 194 } else { 195 // use the last markPrice for the market to initialise stopOrders price 196 if em.LastTradedPrice != nil { 197 stopOrders.PriceUpdated(em.LastTradedPrice.Clone()) 198 } 199 } 200 201 expiringStopOrders := common.NewExpiringOrders() 202 if em.ExpiringStopOrders != nil { 203 expiringStopOrders = common.NewExpiringOrdersFromState(em.ExpiringStopOrders) 204 } 205 // @TODO same as in the non-snapshot market constructor: default to legacy liquidation strategy for the time being 206 // this can be removed once this parameter is no longer optional 207 if mkt.LiquidationStrategy == nil { 208 mkt.LiquidationStrategy = liquidation.GetLegacyStrat() 209 } else if mkt.LiquidationStrategy.DisposalSlippage.IsZero() { 210 // @TODO check for migration from v0.75.8, strictly speaking, not doing so should have the same effect, though... 211 mkt.LiquidationStrategy.DisposalSlippage = mkt.LiquiditySLAParams.PriceRange 212 } 213 214 partyMargin := make(map[string]num.Decimal, len(em.PartyMarginFactors)) 215 for _, pmf := range em.PartyMarginFactors { 216 partyMargin[pmf.Party], _ = num.DecimalFromString(pmf.MarginFactor) 217 } 218 219 now := timeService.GetTimeNow() 220 marketType := mkt.MarketType() 221 222 markPriceCalculator := common.NewCompositePriceCalculatorFromSnapshot(ctx, em.CurrentMarkPrice, timeService, oracleEngine, em.MarkPriceCalculator) 223 224 market := &Market{ 225 log: log, 226 mkt: mkt, 227 closingAt: time.Unix(0, mkt.MarketTimestamps.Close), 228 timeService: timeService, 229 matching: book, 230 tradableInstrument: tradableInstrument, 231 risk: riskEngine, 232 position: positionEngine, 233 settlement: settleEngine, 234 collateral: collateralEngine, 235 broker: broker, 236 fee: feeEngine, 237 referralDiscountRewardService: referralDiscountRewardService, 238 volumeDiscountService: volumeDiscountService, 239 volumeRebateService: volumeRebateService, 240 liquidityEngine: liquidityEngine, 241 liquidity: marketLiquidity, 242 parties: map[string]struct{}{}, 243 tsCalc: tsCalc, 244 feeSplitter: common.NewFeeSplitterFromSnapshot(em.FeeSplitter, now), 245 as: as, 246 pMonitor: pMonitor, 247 peggedOrders: common.NewPeggedOrdersFromSnapshot(log, timeService, em.PeggedOrders), 248 expiringOrders: common.NewExpiringOrdersFromState(em.ExpiringOrders), 249 equityShares: equityShares, 250 lastBestBidPrice: em.LastBestBid.Clone(), 251 lastBestAskPrice: em.LastBestAsk.Clone(), 252 lastMidBuyPrice: em.LastMidBid.Clone(), 253 lastMidSellPrice: em.LastMidAsk.Clone(), 254 lastTradedPrice: em.LastTradedPrice, 255 priceFactor: priceFactor, 256 assetFactor: assetFactor, 257 lastMarketValueProxy: em.LastMarketValueProxy, 258 marketActivityTracker: marketActivityTracker, 259 positionFactor: positionFactor, 260 stateVarEngine: stateVarEngine, 261 settlementDataInMarket: em.SettlementData, 262 settlementAsset: asset, 263 stopOrders: stopOrders, 264 expiringStopOrders: expiringStopOrders, 265 perp: marketType == types.MarketTypePerp, 266 partyMarginFactor: partyMargin, 267 banking: banking, 268 markPriceCalculator: markPriceCalculator, 269 amm: ammEngine, 270 } 271 272 market.markPriceCalculator.NotifyOnDataSourcePropagation(market.dataSourcePropagation) 273 markPriceCalculator.SetOraclePriceScalingFunc(market.scaleOracleData) 274 if fCap := mkt.TradableInstrument.Instrument.Product.Cap(); fCap != nil { 275 market.fCap = fCap 276 market.capMax, _ = num.UintFromDecimal(fCap.MaxPrice.ToDecimal().Mul(priceFactor)) 277 markPriceCalculator.SetMaxPriceCap(market.capMax.Clone()) 278 } 279 280 if em.InternalCompositePriceCalculator != nil { 281 market.internalCompositePriceCalculator = common.NewCompositePriceCalculatorFromSnapshot(ctx, nil, timeService, oracleEngine, em.InternalCompositePriceCalculator) 282 market.internalCompositePriceCalculator.SetOraclePriceScalingFunc(market.scaleOracleData) 283 market.internalCompositePriceCalculator.NotifyOnDataSourcePropagation(market.dataSourcePropagation) 284 } 285 286 le := liquidation.New(log, mkt.LiquidationStrategy, mkt.GetID(), broker, book, as, timeService, positionEngine, pMonitor, market.amm) 287 market.liquidation = le 288 289 for _, p := range em.Parties { 290 market.parties[p] = struct{}{} 291 } 292 293 market.assetDP = uint32(assetDecimals) 294 switch marketType { 295 case types.MarketTypeFuture: 296 market.tradableInstrument.Instrument.Product.NotifyOnTradingTerminated(market.tradingTerminated) 297 market.tradableInstrument.Instrument.Product.NotifyOnSettlementData(market.settlementData) 298 case types.MarketTypePerp: 299 market.tradableInstrument.Instrument.Product.NotifyOnSettlementData(market.settlementDataPerp) 300 market.tradableInstrument.Instrument.Product.NotifyOnDataSourcePropagation(market.productDataSourcePropagation) 301 case types.MarketTypeSpot: 302 default: 303 log.Panic("unexpected market type", logging.Int("type", int(marketType))) 304 } 305 306 if em.SettlementData != nil { 307 // ensure oracle has the settlement data 308 market.tradableInstrument.Instrument.Product.RestoreSettlementData(em.SettlementData.Clone()) 309 } 310 311 liquidityEngine.SetGetStaticPricesFunc(market.getBestStaticPricesDecimal) 312 313 if mkt.State == types.MarketStateTradingTerminated { 314 market.tradableInstrument.Instrument.UnsubscribeTradingTerminated(ctx) 315 } 316 317 if em.Closed { 318 market.closed = true 319 market.tradableInstrument.Instrument.Unsubscribe(ctx) 320 market.markPriceCalculator.Close(ctx) 321 if market.internalCompositePriceCalculator != nil { 322 market.internalCompositePriceCalculator.Close(ctx) 323 } 324 stateVarEngine.UnregisterStateVariable(asset, mkt.ID) 325 } 326 return market, nil 327 } 328 329 func (m *Market) GetNewStateProviders() []types.StateProvider { 330 return []types.StateProvider{ 331 m.position, m.matching, m.tsCalc, 332 m.liquidityEngine.V1StateProvider(), m.liquidityEngine.V2StateProvider(), 333 m.settlement, m.liquidation, 334 } 335 } 336 337 func (m *Market) GetState() *types.ExecMarket { 338 rf := m.risk.GetRiskFactors() 339 var sp *num.Numeric 340 if m.settlementDataInMarket != nil { 341 sp = m.settlementDataInMarket.Clone() 342 } 343 344 parties := maps.Keys(m.parties) 345 sort.Strings(parties) 346 assetQuantum, _ := m.collateral.GetAssetQuantum(m.settlementAsset) 347 348 partyMarginFactors := make([]*snapshot.PartyMarginFactor, 0, len(m.partyMarginFactor)) 349 for k, d := range m.partyMarginFactor { 350 partyMarginFactors = append(partyMarginFactors, &snapshot.PartyMarginFactor{Party: k, MarginFactor: d.String()}) 351 } 352 sort.Slice(partyMarginFactors, func(i, j int) bool { 353 return partyMarginFactors[i].Party < partyMarginFactors[j].Party 354 }) 355 356 em := &types.ExecMarket{ 357 Market: m.mkt.DeepClone(), 358 PriceMonitor: m.pMonitor.GetState(), 359 AuctionState: m.as.GetState(), 360 PeggedOrders: m.peggedOrders.GetState(), 361 ExpiringOrders: m.expiringOrders.GetState(), 362 LastBestBid: m.lastBestBidPrice.Clone(), 363 LastBestAsk: m.lastBestAskPrice.Clone(), 364 LastMidBid: m.lastMidBuyPrice.Clone(), 365 LastMidAsk: m.lastMidSellPrice.Clone(), 366 LastMarketValueProxy: m.lastMarketValueProxy, 367 LastTradedPrice: m.lastTradedPrice, 368 EquityShare: m.equityShares.GetState(), 369 RiskFactorConsensusReached: m.risk.IsRiskFactorInitialised(), 370 ShortRiskFactor: rf.Short, 371 LongRiskFactor: rf.Long, 372 FeeSplitter: m.feeSplitter.GetState(), 373 SettlementData: sp, 374 NextMTM: m.nextMTM.UnixNano(), 375 NextInternalCompositePriceCalc: m.nextInternalCompositePriceCalc.UnixNano(), 376 Parties: parties, 377 Closed: m.closed, 378 IsSucceeded: m.succeeded, 379 StopOrders: m.stopOrders.ToProto(), 380 ExpiringStopOrders: m.expiringStopOrders.GetState(), 381 Product: m.tradableInstrument.Instrument.Product.Serialize(), 382 FeesStats: m.fee.GetState(assetQuantum), 383 PartyMarginFactors: partyMarginFactors, 384 MarkPriceCalculator: m.markPriceCalculator.IntoProto(), 385 Amm: m.amm.IntoProto(), 386 MarketLiquidity: m.liquidity.GetState(), 387 } 388 if m.perp && m.internalCompositePriceCalculator != nil { 389 em.InternalCompositePriceCalculator = m.internalCompositePriceCalculator.IntoProto() 390 } 391 392 return em 393 }