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  }