code.vegaprotocol.io/vega@v0.79.0/core/execution/future/market.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  	"errors"
    21  	"fmt"
    22  	"sort"
    23  	"strconv"
    24  	"sync"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/core/assets"
    28  	"code.vegaprotocol.io/vega/core/collateral"
    29  	"code.vegaprotocol.io/vega/core/events"
    30  	"code.vegaprotocol.io/vega/core/execution/amm"
    31  	"code.vegaprotocol.io/vega/core/execution/common"
    32  	"code.vegaprotocol.io/vega/core/execution/liquidation"
    33  	"code.vegaprotocol.io/vega/core/execution/stoporders"
    34  	"code.vegaprotocol.io/vega/core/fee"
    35  	"code.vegaprotocol.io/vega/core/idgeneration"
    36  	liquiditytarget "code.vegaprotocol.io/vega/core/liquidity/target"
    37  	"code.vegaprotocol.io/vega/core/liquidity/v2"
    38  	"code.vegaprotocol.io/vega/core/markets"
    39  	"code.vegaprotocol.io/vega/core/matching"
    40  	"code.vegaprotocol.io/vega/core/metrics"
    41  	"code.vegaprotocol.io/vega/core/monitor"
    42  	"code.vegaprotocol.io/vega/core/monitor/price"
    43  	"code.vegaprotocol.io/vega/core/positions"
    44  	"code.vegaprotocol.io/vega/core/products"
    45  	"code.vegaprotocol.io/vega/core/risk"
    46  	"code.vegaprotocol.io/vega/core/settlement"
    47  	"code.vegaprotocol.io/vega/core/types"
    48  	"code.vegaprotocol.io/vega/core/types/statevar"
    49  	vegacontext "code.vegaprotocol.io/vega/libs/context"
    50  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    51  	"code.vegaprotocol.io/vega/libs/crypto"
    52  	"code.vegaprotocol.io/vega/libs/num"
    53  	"code.vegaprotocol.io/vega/libs/ptr"
    54  	"code.vegaprotocol.io/vega/logging"
    55  	"code.vegaprotocol.io/vega/protos/vega"
    56  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    57  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    58  
    59  	"golang.org/x/exp/maps"
    60  )
    61  
    62  // TargetStakeCalculator interface.
    63  type TargetStakeCalculator interface {
    64  	types.StateProvider
    65  	RecordOpenInterest(oi uint64, now time.Time) error
    66  	GetTargetStake(rf types.RiskFactor, now time.Time, markPrice *num.Uint) *num.Uint
    67  	GetTheoreticalTargetStake(rf types.RiskFactor, now time.Time, markPrice *num.Uint, trades []*types.Trade) *num.Uint
    68  	UpdateScalingFactor(sFactor num.Decimal) error
    69  	UpdateTimeWindow(tWindow time.Duration)
    70  	StopSnapshots()
    71  	UpdateParameters(types.TargetStakeParameters)
    72  }
    73  
    74  // Market represents an instance of a market in vega and is in charge of calling
    75  // the engines in order to process all transactions.
    76  type Market struct {
    77  	log   *logging.Logger
    78  	idgen common.IDGenerator
    79  
    80  	mkt *types.Market
    81  
    82  	closingAt   time.Time
    83  	timeService common.TimeService
    84  
    85  	mu            sync.RWMutex
    86  	markPriceLock sync.RWMutex
    87  
    88  	lastTradedPrice *num.Uint
    89  	priceFactor     num.Decimal
    90  	assetFactor     num.Decimal
    91  
    92  	// own engines
    93  	matching                      *matching.CachedOrderBook
    94  	tradableInstrument            *markets.TradableInstrument
    95  	risk                          *risk.Engine
    96  	position                      *positions.SnapshotEngine
    97  	settlement                    *settlement.SnapshotEngine
    98  	fee                           *fee.Engine
    99  	referralDiscountRewardService fee.ReferralDiscountRewardService
   100  	volumeDiscountService         fee.VolumeDiscountService
   101  	volumeRebateService           fee.VolumeRebateService
   102  	liquidity                     *common.MarketLiquidity
   103  	liquidityEngine               common.LiquidityEngine
   104  
   105  	// deps engines
   106  	collateral common.Collateral
   107  	banking    common.Banking
   108  
   109  	broker               common.Broker
   110  	closed               bool
   111  	finalFeesDistributed bool
   112  
   113  	parties map[string]struct{}
   114  
   115  	pMonitor common.PriceMonitor
   116  
   117  	tsCalc TargetStakeCalculator
   118  
   119  	as common.AuctionState
   120  
   121  	peggedOrders   *common.PeggedOrders
   122  	expiringOrders *common.ExpiringOrders
   123  
   124  	// Store the previous price values so we can see what has changed
   125  	lastBestBidPrice *num.Uint
   126  	lastBestAskPrice *num.Uint
   127  	lastMidBuyPrice  *num.Uint
   128  	lastMidSellPrice *num.Uint
   129  
   130  	bondPenaltyFactor                     num.Decimal
   131  	lastMarketValueProxy                  num.Decimal
   132  	marketValueWindowLength               time.Duration
   133  	minMaintenanceMarginQuantumMultiplier num.Decimal
   134  
   135  	// Liquidity Fee
   136  	feeSplitter  *common.FeeSplitter
   137  	equityShares *common.EquityShares
   138  
   139  	stateVarEngine        common.StateVarEngine
   140  	marketActivityTracker *common.MarketActivityTracker
   141  	positionFactor        num.Decimal // 10^pdp
   142  	assetDP               uint32
   143  
   144  	settlementDataInMarket          *num.Numeric
   145  	nextMTM                         time.Time
   146  	nextInternalCompositePriceCalc  time.Time
   147  	mtmDelta                        time.Duration
   148  	internalCompositePriceFrequency time.Duration
   149  
   150  	settlementAsset string
   151  	succeeded       bool
   152  
   153  	maxStopOrdersPerParties *num.Uint
   154  	stopOrders              *stoporders.Pool
   155  	expiringStopOrders      *common.ExpiringOrders
   156  
   157  	minDuration time.Duration
   158  	perp        bool
   159  
   160  	stats       *types.MarketStats
   161  	liquidation *liquidation.Engine // @TODO probably should be an interface for unit testing
   162  
   163  	// set to false when started
   164  	// we'll use it only once after an upgrade
   165  	// to make sure the migraton from the upgrade
   166  	// are applied properly
   167  	ensuredMigration73 bool
   168  	epoch              types.Epoch
   169  
   170  	// party ID to isolated margin factor
   171  	partyMarginFactor                map[string]num.Decimal
   172  	markPriceCalculator              *common.CompositePriceCalculator
   173  	internalCompositePriceCalculator *common.CompositePriceCalculator
   174  
   175  	amm *amm.Engine
   176  
   177  	fCap   *types.FutureCap
   178  	capMax *num.Uint
   179  }
   180  
   181  // NewMarket creates a new market using the market framework configuration and creates underlying engines.
   182  func NewMarket(
   183  	ctx context.Context,
   184  	log *logging.Logger,
   185  	riskConfig risk.Config,
   186  	positionConfig positions.Config,
   187  	settlementConfig settlement.Config,
   188  	matchingConfig matching.Config,
   189  	feeConfig fee.Config,
   190  	liquidityConfig liquidity.Config,
   191  	collateralEngine common.Collateral,
   192  	oracleEngine products.OracleEngine,
   193  	mkt *types.Market,
   194  	timeService common.TimeService,
   195  	broker common.Broker,
   196  	auctionState *monitor.AuctionState,
   197  	stateVarEngine common.StateVarEngine,
   198  	marketActivityTracker *common.MarketActivityTracker,
   199  	assetDetails *assets.Asset,
   200  	peggedOrderNotify func(int64),
   201  	referralDiscountRewardService fee.ReferralDiscountRewardService,
   202  	volumeDiscountService fee.VolumeDiscountService,
   203  	volumeRebateService fee.VolumeRebateService,
   204  	banking common.Banking,
   205  	parties common.Parties,
   206  ) (*Market, error) {
   207  	if len(mkt.ID) == 0 {
   208  		return nil, common.ErrEmptyMarketID
   209  	}
   210  
   211  	assetDecimals := assetDetails.DecimalPlaces()
   212  	positionFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(mkt.PositionDecimalPlaces))
   213  
   214  	tradableInstrument, err := markets.NewTradableInstrument(ctx, log, mkt.TradableInstrument, mkt.ID, timeService, oracleEngine, broker, uint32(assetDecimals))
   215  	if err != nil {
   216  		return nil, fmt.Errorf("unable to instantiate a new market: %w", err)
   217  	}
   218  	priceFactor := num.DecimalOne()
   219  	if exp := int(assetDecimals) - int(mkt.DecimalPlaces); exp != 0 {
   220  		priceFactor = num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(exp)))
   221  	}
   222  	assetFactor := num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(assetDecimals)))
   223  
   224  	asset := tradableInstrument.Instrument.Product.GetAsset()
   225  	positionEngine := positions.NewSnapshotEngine(log, positionConfig, mkt.ID, broker)
   226  
   227  	ammEngine := amm.New(
   228  		log,
   229  		broker,
   230  		collateralEngine,
   231  		mkt.GetID(),
   232  		asset,
   233  		positionEngine,
   234  		priceFactor,
   235  		positionFactor,
   236  		marketActivityTracker,
   237  		parties,
   238  		mkt.AllowedEmptyAmmLevels,
   239  	)
   240  
   241  	// @TODO -> the raw auctionstate shouldn't be something exposed to the matching engine
   242  	// as far as matching goes: it's either an auction or not
   243  	book := matching.NewCachedOrderBook(log, matchingConfig, mkt.ID, auctionState.InAuction(), peggedOrderNotify)
   244  	book.SetOffbookSource(ammEngine)
   245  
   246  	riskEngine := risk.NewEngine(log,
   247  		riskConfig,
   248  		tradableInstrument.MarginCalculator,
   249  		tradableInstrument.RiskModel,
   250  		book,
   251  		auctionState,
   252  		timeService,
   253  		broker,
   254  		mkt.ID,
   255  		asset,
   256  		stateVarEngine,
   257  		positionFactor,
   258  		false,
   259  		nil,
   260  		mkt.LinearSlippageFactor,
   261  		mkt.QuadraticSlippageFactor,
   262  	)
   263  
   264  	settleEngine := settlement.NewSnapshotEngine(
   265  		log,
   266  		settlementConfig,
   267  		tradableInstrument.Instrument.Product,
   268  		mkt.ID,
   269  		timeService,
   270  		broker,
   271  		positionFactor,
   272  	)
   273  
   274  	feeEngine, err := fee.New(log, feeConfig, *mkt.Fees, asset, positionFactor)
   275  	if err != nil {
   276  		return nil, fmt.Errorf("unable to instantiate fee engine: %w", err)
   277  	}
   278  
   279  	tsCalc := liquiditytarget.NewSnapshotEngine(*mkt.LiquidityMonitoringParameters.TargetStakeParameters, positionEngine, mkt.ID, positionFactor)
   280  
   281  	pMonitor, err := price.NewMonitor(asset, mkt.ID, tradableInstrument.RiskModel, auctionState, mkt.PriceMonitoringSettings, stateVarEngine, log)
   282  	if err != nil {
   283  		return nil, fmt.Errorf("unable to instantiate price monitoring engine: %w", err)
   284  	}
   285  
   286  	now := timeService.GetTimeNow()
   287  
   288  	liquidityEngine := liquidity.NewSnapshotEngine(
   289  		liquidityConfig, log, timeService, broker, tradableInstrument.RiskModel,
   290  		pMonitor, book, auctionState, asset, mkt.ID, stateVarEngine, positionFactor, mkt.LiquiditySLAParams)
   291  
   292  	equityShares := common.NewEquityShares(num.DecimalZero())
   293  
   294  	marketLiquidity := common.NewMarketLiquidity(
   295  		log, liquidityEngine, collateralEngine, broker, book, equityShares, marketActivityTracker,
   296  		feeEngine, common.FutureMarketType, mkt.ID, asset, priceFactor, mkt.LiquiditySLAParams.PriceRange,
   297  		ammEngine,
   298  	)
   299  
   300  	// The market is initially created in a proposed state
   301  	mkt.State = types.MarketStateProposed
   302  	mkt.TradingMode = types.MarketTradingModeNoTrading
   303  
   304  	pending, open := auctionState.GetAuctionBegin(), auctionState.GetAuctionEnd()
   305  	// Populate the market timestamps
   306  	ts := &types.MarketTimestamps{
   307  		Proposed: now.UnixNano(),
   308  		Pending:  now.UnixNano(),
   309  	}
   310  	if pending != nil {
   311  		ts.Pending = pending.UnixNano()
   312  	}
   313  	if open != nil {
   314  		ts.Open = open.UnixNano()
   315  	}
   316  
   317  	mkt.MarketTimestamps = ts
   318  	// @TODO remove this once liquidation strategy is no longer optional
   319  	// consider mkt.LiquidationStrategy is currently still treated as optional, but we use
   320  	// mkt in the events we're sending to data-node, let's set the default strategy here
   321  	// and update the mkt object so the events will accurately reflect what this is being set to
   322  	if mkt.LiquidationStrategy == nil {
   323  		mkt.LiquidationStrategy = liquidation.GetLegacyStrat()
   324  	}
   325  
   326  	marketType := mkt.MarketType()
   327  	market := &Market{
   328  		log:                           log,
   329  		idgen:                         nil,
   330  		mkt:                           mkt,
   331  		matching:                      book,
   332  		tradableInstrument:            tradableInstrument,
   333  		risk:                          riskEngine,
   334  		position:                      positionEngine,
   335  		settlement:                    settleEngine,
   336  		collateral:                    collateralEngine,
   337  		timeService:                   timeService,
   338  		broker:                        broker,
   339  		fee:                           feeEngine,
   340  		liquidity:                     marketLiquidity,
   341  		liquidityEngine:               liquidityEngine, // TODO karel - consider not having this
   342  		parties:                       map[string]struct{}{},
   343  		as:                            auctionState,
   344  		pMonitor:                      pMonitor,
   345  		tsCalc:                        tsCalc,
   346  		peggedOrders:                  common.NewPeggedOrders(log, timeService),
   347  		expiringOrders:                common.NewExpiringOrders(),
   348  		feeSplitter:                   common.NewFeeSplitter(),
   349  		equityShares:                  equityShares,
   350  		lastBestAskPrice:              num.UintZero(),
   351  		lastMidSellPrice:              num.UintZero(),
   352  		lastMidBuyPrice:               num.UintZero(),
   353  		lastBestBidPrice:              num.UintZero(),
   354  		stateVarEngine:                stateVarEngine,
   355  		marketActivityTracker:         marketActivityTracker,
   356  		priceFactor:                   priceFactor,
   357  		assetFactor:                   assetFactor,
   358  		positionFactor:                positionFactor,
   359  		nextMTM:                       time.Time{}, // default to zero time
   360  		maxStopOrdersPerParties:       num.UintZero(),
   361  		stopOrders:                    stoporders.New(log),
   362  		expiringStopOrders:            common.NewExpiringOrders(),
   363  		perp:                          marketType == types.MarketTypePerp,
   364  		referralDiscountRewardService: referralDiscountRewardService,
   365  		volumeDiscountService:         volumeDiscountService,
   366  		volumeRebateService:           volumeRebateService,
   367  		partyMarginFactor:             map[string]num.Decimal{},
   368  		banking:                       banking,
   369  		markPriceCalculator:           common.NewCompositePriceCalculator(ctx, mkt.MarkPriceConfiguration, oracleEngine, timeService),
   370  		amm:                           ammEngine,
   371  	}
   372  
   373  	le := liquidation.New(log, mkt.LiquidationStrategy, mkt.GetID(), broker, book, auctionState, timeService, positionEngine, pMonitor, market.amm)
   374  	market.liquidation = le
   375  
   376  	market.markPriceCalculator.SetOraclePriceScalingFunc(market.scaleOracleData)
   377  	if fCap := mkt.TradableInstrument.Instrument.Product.Cap(); fCap != nil {
   378  		market.fCap = fCap
   379  		market.capMax, _ = num.UintFromDecimal(fCap.MaxPrice.ToDecimal().Mul(priceFactor))
   380  		market.markPriceCalculator.SetMaxPriceCap(market.capMax.Clone())
   381  	}
   382  
   383  	if market.IsPerp() {
   384  		internalCompositePriceConfig := mkt.TradableInstrument.Instrument.GetPerps().InternalCompositePriceConfig
   385  		if internalCompositePriceConfig != nil {
   386  			market.internalCompositePriceCalculator = common.NewCompositePriceCalculator(ctx, internalCompositePriceConfig, oracleEngine, timeService)
   387  			market.internalCompositePriceCalculator.SetOraclePriceScalingFunc(market.scaleOracleData)
   388  
   389  			market.internalCompositePriceCalculator.NotifyOnDataSourcePropagation(market.dataSourcePropagation)
   390  		}
   391  	}
   392  
   393  	market.markPriceCalculator.NotifyOnDataSourcePropagation(market.dataSourcePropagation)
   394  
   395  	// now set AMM engine on liquidity market.
   396  	market.liquidity.SetAMM(market.amm)
   397  
   398  	assets, _ := mkt.GetAssets()
   399  	market.settlementAsset = assets[0]
   400  
   401  	liquidityEngine.SetGetStaticPricesFunc(market.getBestStaticPricesDecimal)
   402  
   403  	switch marketType {
   404  	case types.MarketTypeFuture:
   405  		market.tradableInstrument.Instrument.Product.NotifyOnTradingTerminated(market.tradingTerminated)
   406  		market.tradableInstrument.Instrument.Product.NotifyOnSettlementData(market.settlementData)
   407  	case types.MarketTypePerp:
   408  		market.tradableInstrument.Instrument.Product.NotifyOnSettlementData(market.settlementDataPerp)
   409  		market.tradableInstrument.Instrument.Product.NotifyOnDataSourcePropagation(market.productDataSourcePropagation)
   410  	case types.MarketTypeSpot:
   411  	default:
   412  		log.Panic("unexpected market type", logging.Int("type", int(marketType)))
   413  	}
   414  	market.assetDP = uint32(assetDecimals)
   415  	return market, nil
   416  }
   417  
   418  func (m *Market) OnEpochEvent(ctx context.Context, epoch types.Epoch) {
   419  	if m.closed {
   420  		return
   421  	}
   422  
   423  	switch epoch.Action {
   424  	case vegapb.EpochAction_EPOCH_ACTION_START:
   425  		m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams)
   426  		m.liquidity.OnEpochStart(ctx, m.timeService.GetTimeNow(), m.markPriceCalculator.GetPrice(), m.midPrice(), m.getTargetStake(), m.positionFactor)
   427  		m.epoch = epoch
   428  	case vegapb.EpochAction_EPOCH_ACTION_END:
   429  		// compute parties stats for the previous epoch
   430  		m.onEpochEndPartiesStats()
   431  		if !m.finalFeesDistributed {
   432  			m.liquidity.OnEpochEnd(ctx, m.timeService.GetTimeNow(), epoch)
   433  		}
   434  
   435  		m.banking.RegisterTradingFees(ctx, m.settlementAsset, m.fee.TotalTradingFeesPerParty())
   436  
   437  		assetQuantum, _ := m.collateral.GetAssetQuantum(m.settlementAsset)
   438  		feesStats := m.fee.GetFeesStatsOnEpochEnd(assetQuantum)
   439  		feesStats.Market = m.GetID()
   440  		feesStats.EpochSeq = epoch.Seq
   441  
   442  		m.broker.Send(events.NewFeesStatsEvent(ctx, feesStats))
   443  	}
   444  
   445  	m.updateLiquidityFee(ctx)
   446  }
   447  
   448  func (m *Market) ClosePosition(context.Context, string) bool {
   449  	return true
   450  }
   451  
   452  func (m *Market) OnEpochRestore(ctx context.Context, epoch types.Epoch) {
   453  	m.epoch = epoch
   454  	m.liquidityEngine.OnEpochRestore(epoch)
   455  }
   456  
   457  func (m *Market) IsOpeningAuction() bool {
   458  	return m.as.IsOpeningAuction()
   459  }
   460  
   461  func (m *Market) onEpochEndPartiesStats() {
   462  	if m.markPriceCalculator.GetPrice() == nil {
   463  		// no mark price yet, so no reason to calculate any of those
   464  		return
   465  	}
   466  
   467  	if m.stats == nil {
   468  		m.stats = &types.MarketStats{}
   469  	}
   470  
   471  	m.stats.PartiesOpenNotionalVolume = map[string]*num.Uint{}
   472  	m.stats.PartiesTotalTradeVolume = map[string]*num.Uint{}
   473  
   474  	assetQuantum, err := m.collateral.GetAssetQuantum(m.settlementAsset)
   475  	if err != nil {
   476  		m.log.Panic("couldn't get quantum for asset",
   477  			logging.MarketID(m.mkt.ID),
   478  			logging.AssetID(m.settlementAsset),
   479  		)
   480  	}
   481  
   482  	// first get the open interest per party
   483  	partiesOpenInterest := m.position.GetPartiesLowestOpenInterestForEpoch()
   484  	for p, oi := range partiesOpenInterest {
   485  		// volume
   486  		openInterestVolume := num.UintZero().Mul(num.NewUint(oi), m.markPriceCalculator.GetPrice())
   487  		// scale to position decimal
   488  		scaledOpenInterest := openInterestVolume.ToDecimal().Div(m.positionFactor)
   489  		// apply quantum
   490  		m.stats.PartiesOpenNotionalVolume[p], _ = num.UintFromDecimal(
   491  			scaledOpenInterest.Div(assetQuantum),
   492  		)
   493  	}
   494  
   495  	// first get the open interest per party
   496  	partiesTradedVolume := m.position.GetPartiesTradedVolumeForEpoch()
   497  	for p, oi := range partiesTradedVolume {
   498  		// volume
   499  		tradedVolume := num.UintZero().Mul(num.NewUint(oi), m.markPriceCalculator.GetPrice())
   500  		// scale to position decimal
   501  		scaledOpenInterest := tradedVolume.ToDecimal().Div(m.positionFactor)
   502  		// apply quantum
   503  		m.stats.PartiesTotalTradeVolume[p], _ = num.UintFromDecimal(
   504  			scaledOpenInterest.Div(assetQuantum),
   505  		)
   506  	}
   507  }
   508  
   509  func (m *Market) BeginBlock(ctx context.Context) {
   510  	if m.ensuredMigration73 {
   511  		return
   512  	}
   513  	m.ensuredMigration73 = true
   514  
   515  	// TODO(jeremy): remove this after the 72 upgrade
   516  	oevents := []events.Event{}
   517  	for _, oid := range m.liquidityEngine.GetLegacyOrders() {
   518  		order, foundOnBook, err := m.getOrderByID(oid)
   519  		if err != nil {
   520  			continue // err here is ErrOrderNotFound
   521  		}
   522  		if !foundOnBook {
   523  			m.log.Panic("lp order was in the pegged order list?", logging.Order(order))
   524  		}
   525  
   526  		cancellation, err := m.matching.CancelOrder(order)
   527  		if cancellation == nil || err != nil {
   528  			m.log.Panic("Failure after cancel order from matching engine",
   529  				logging.String("party-id", order.Party),
   530  				logging.String("order-id", oid),
   531  				logging.String("market", m.mkt.ID),
   532  				logging.Error(err))
   533  		}
   534  
   535  		_ = m.position.UnregisterOrder(ctx, order)
   536  		order.Status = types.OrderStatusCancelled
   537  		oevents = append(oevents, events.NewOrderEvent(ctx, order))
   538  	}
   539  
   540  	if len(oevents) > 0 {
   541  		m.broker.SendBatch(oevents)
   542  	}
   543  
   544  	// TODO(jeremy): This bit is here specifically to create account
   545  	// which should have been create with the normal process of
   546  	// submitting liquidity provisions for the market.
   547  	// should probably be removed in the near future (aft this release)
   548  	lpParties := maps.Keys(m.liquidityEngine.ProvisionsPerParty())
   549  	sort.Strings(lpParties)
   550  
   551  	for _, p := range lpParties {
   552  		_, err := m.collateral.GetOrCreatePartyLiquidityFeeAccount(
   553  			ctx, p, m.GetID(), m.GetSettlementAsset())
   554  		if err != nil {
   555  			m.log.Panic("couldn't create party liquidity fee account")
   556  		}
   557  	}
   558  
   559  	_, err := m.collateral.GetOrCreateLiquidityFeesBonusDistributionAccount(ctx, m.GetID(), m.GetSettlementAsset())
   560  	if err != nil {
   561  		m.log.Panic("failed to get bonus distribution account", logging.Error(err))
   562  	}
   563  }
   564  
   565  func (m *Market) GetAssets() []string {
   566  	return []string{m.settlementAsset}
   567  }
   568  
   569  // GetPartiesStats is called at the end of the epoch, only once to
   570  // be sent to the activity streak engine. This is using the calculated
   571  // at the end of the epoch based on the countrer in the position engine.
   572  // This is never sent into a snapshot as it relies on the order the
   573  // epoch callback are executed. We expect the market OnEpoch to be called
   574  // first, and compute the data, then the activity tracker callback to be
   575  // called next, and retrieve the data through this method.
   576  // The stats are reseted before being returned.
   577  func (m *Market) GetPartiesStats() (stats *types.MarketStats) {
   578  	stats, m.stats = m.stats, &types.MarketStats{}
   579  
   580  	return stats
   581  }
   582  
   583  func (m *Market) IsSucceeded() bool {
   584  	return m.succeeded
   585  }
   586  
   587  func (m *Market) IsPerp() bool {
   588  	return m.perp
   589  }
   590  
   591  func (m *Market) StopSnapshots() {
   592  	m.matching.StopSnapshots()
   593  	m.position.StopSnapshots()
   594  	m.liquidityEngine.StopSnapshots()
   595  	m.settlement.StopSnapshots()
   596  	m.tsCalc.StopSnapshots()
   597  	m.liquidation.StopSnapshots()
   598  }
   599  
   600  func (m *Market) Mkt() *types.Market {
   601  	return m.mkt
   602  }
   603  
   604  func (m *Market) GetEquityShares() *common.EquityShares {
   605  	return m.equityShares
   606  }
   607  
   608  func (m *Market) GetEquitySharesForParty(partyID string) num.Decimal {
   609  	primary := m.equityShares.SharesFromParty(partyID)
   610  	if sub, err := m.amm.GetAMMParty(partyID); err == nil {
   611  		return primary.Add(m.equityShares.SharesFromParty(sub))
   612  	}
   613  	return primary
   614  }
   615  
   616  func (m *Market) ResetParentIDAndInsurancePoolFraction() {
   617  	m.mkt.ParentMarketID = ""
   618  	m.mkt.InsurancePoolFraction = num.DecimalZero()
   619  }
   620  
   621  func (m *Market) GetParentMarketID() string {
   622  	return m.mkt.ParentMarketID
   623  }
   624  
   625  func (m *Market) GetInsurancePoolFraction() num.Decimal {
   626  	return m.mkt.InsurancePoolFraction
   627  }
   628  
   629  func (m *Market) SetSucceeded() {
   630  	m.succeeded = true
   631  }
   632  
   633  func (m *Market) SetNextInternalCompositePriceCalc(tm time.Time) {
   634  	m.nextInternalCompositePriceCalc = tm
   635  }
   636  
   637  func (m *Market) SetNextMTM(tm time.Time) {
   638  	m.nextMTM = tm
   639  }
   640  
   641  func (m *Market) GetNextMTM() time.Time {
   642  	return m.nextMTM
   643  }
   644  
   645  func (m *Market) GetSettlementAsset() string {
   646  	return m.settlementAsset
   647  }
   648  
   649  func (m *Market) Update(ctx context.Context, config *types.Market, oracleEngine products.OracleEngine) error {
   650  	tickSizeChanged := config.TickSize.NEQ(m.mkt.TickSize)
   651  
   652  	config.TradingMode = m.mkt.TradingMode
   653  	config.State = m.mkt.State
   654  	config.MarketTimestamps = m.mkt.MarketTimestamps
   655  	recalcMargins := !config.TradableInstrument.RiskModel.Equal(m.mkt.TradableInstrument.RiskModel)
   656  	// update the liquidation strategy if required, ideally we want to use .LiquidationStrategy.EQ(), but that breaks the integration tests
   657  	// as the market config pointer is shared
   658  	if config.LiquidationStrategy != nil {
   659  		m.liquidation.Update(config.LiquidationStrategy)
   660  	}
   661  	m.mkt = config
   662  	assets, _ := config.GetAssets()
   663  	m.settlementAsset = assets[0]
   664  
   665  	if err := m.tradableInstrument.UpdateInstrument(ctx, m.log, m.mkt.TradableInstrument, m.GetID(), oracleEngine, m.broker); err != nil {
   666  		return err
   667  	}
   668  	m.risk.UpdateModel(m.stateVarEngine, m.tradableInstrument.MarginCalculator, m.tradableInstrument.RiskModel, m.mkt.LinearSlippageFactor, m.mkt.QuadraticSlippageFactor)
   669  	m.settlement.UpdateProduct(m.tradableInstrument.Instrument.Product)
   670  	m.tsCalc.UpdateParameters(*m.mkt.LiquidityMonitoringParameters.TargetStakeParameters)
   671  	m.pMonitor.UpdateSettings(m.tradableInstrument.RiskModel, m.mkt.PriceMonitoringSettings, m.as)
   672  	m.liquidity.UpdateMarketConfig(m.tradableInstrument.RiskModel, m.pMonitor)
   673  	m.amm.UpdateAllowedEmptyLevels(m.mkt.AllowedEmptyAmmLevels)
   674  
   675  	if err := m.markPriceCalculator.UpdateConfig(ctx, oracleEngine, m.mkt.MarkPriceConfiguration); err != nil {
   676  		m.markPriceCalculator.SetOraclePriceScalingFunc(m.scaleOracleData)
   677  		return err
   678  	}
   679  	if m.IsPerp() {
   680  		internalCompositePriceConfig := m.mkt.TradableInstrument.Instrument.GetPerps().InternalCompositePriceConfig
   681  		if internalCompositePriceConfig == nil && m.internalCompositePriceCalculator != nil {
   682  			// unsubscribe existing oracles if any
   683  			m.internalCompositePriceCalculator.UpdateConfig(ctx, oracleEngine, nil)
   684  			m.internalCompositePriceCalculator = nil
   685  		} else if m.internalCompositePriceCalculator != nil {
   686  			// there was previously a intenal composite price calculator
   687  			if err := m.internalCompositePriceCalculator.UpdateConfig(ctx, oracleEngine, internalCompositePriceConfig); err != nil {
   688  				m.internalCompositePriceCalculator.SetOraclePriceScalingFunc(m.scaleOracleData)
   689  				return err
   690  			}
   691  		} else if internalCompositePriceConfig != nil {
   692  			// it's a new index calculator
   693  			m.internalCompositePriceCalculator = common.NewCompositePriceCalculator(ctx, internalCompositePriceConfig, oracleEngine, m.timeService)
   694  			m.internalCompositePriceCalculator.SetOraclePriceScalingFunc(m.scaleOracleData)
   695  		}
   696  	}
   697  
   698  	// we should not need to rebind a replacement oracle here, the m.tradableInstrument.UpdateInstrument
   699  	// call handles the callbacks for us. We only need to check the market state and unbind if needed
   700  	switch m.mkt.State {
   701  	case types.MarketStateTradingTerminated:
   702  		if !m.perp {
   703  			m.tradableInstrument.Instrument.UnsubscribeTradingTerminated(ctx)
   704  			// never hurts to check margins on a terminated, but unsettled market
   705  			recalcMargins = true
   706  		}
   707  	case types.MarketStateSettled:
   708  		// market is settled, unsubscribe all
   709  		m.tradableInstrument.Instrument.Unsubscribe(ctx)
   710  	}
   711  	if tickSizeChanged {
   712  		peggedOrders := m.matching.GetActivePeggedOrderIDs()
   713  		peggedOrders = append(peggedOrders, m.peggedOrders.GetParkedIDs()...)
   714  
   715  		tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor))
   716  		for _, po := range peggedOrders {
   717  			order, err := m.matching.GetOrderByID(po)
   718  			if err != nil {
   719  				order = m.peggedOrders.GetParkedByID(po)
   720  				if order == nil {
   721  					continue
   722  				}
   723  			}
   724  			offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor))
   725  			if !num.UintZero().Mod(order.PeggedOrder.Offset, m.mkt.TickSize).IsZero() ||
   726  				(order.PeggedOrder.Reference == types.PeggedReferenceMid && offsetInAsset.IsZero() && tickSizeInAsset.IsZero()) {
   727  				m.cancelOrder(ctx, order.Party, order.ID)
   728  			}
   729  		}
   730  	}
   731  	m.updateLiquidityFee(ctx)
   732  	// risk model hasn't changed -> return
   733  	if !recalcMargins {
   734  		return nil
   735  	}
   736  	// We know the risk model has been updated, so we have to recalculate margin requirements
   737  	m.recheckMargin(ctx, m.position.Positions())
   738  
   739  	// update immediately during opening auction
   740  	if m.as.IsOpeningAuction() {
   741  		m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams)
   742  	}
   743  
   744  	return nil
   745  }
   746  
   747  func (m *Market) IntoType() types.Market {
   748  	return *m.mkt.DeepClone()
   749  }
   750  
   751  func (m *Market) Hash() []byte {
   752  	mID := logging.String("market-id", m.GetID())
   753  	matchingHash := m.matching.Hash()
   754  	m.log.Debug("orderbook state hash", logging.Hash(matchingHash), mID)
   755  
   756  	positionHash := m.position.Hash()
   757  	m.log.Debug("positions state hash", logging.Hash(positionHash), mID)
   758  
   759  	return crypto.Hash(append(matchingHash, positionHash...))
   760  }
   761  
   762  func (m *Market) GetMarketState() types.MarketState {
   763  	return m.mkt.State
   764  }
   765  
   766  // priceToMarketPrecision
   767  // It should never return a nil pointer.
   768  func (m *Market) priceToMarketPrecision(price *num.Uint) *num.Uint {
   769  	p, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))
   770  	return p
   771  }
   772  
   773  func (m *Market) midPrice() *num.Uint {
   774  	bestBidPrice, _, _ := m.matching.BestBidPriceAndVolume()
   775  	bestOfferPrice, _, _ := m.matching.BestOfferPriceAndVolume()
   776  	two := num.NewUint(2)
   777  	midPrice := num.UintZero()
   778  	if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() {
   779  		midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two)
   780  	}
   781  	return midPrice
   782  }
   783  
   784  func (m *Market) GetMarketData() types.MarketData {
   785  	bestBidPrice, bestBidVolume, _ := m.matching.BestBidPriceAndVolume()
   786  	bestOfferPrice, bestOfferVolume, _ := m.matching.BestOfferPriceAndVolume()
   787  	bestStaticBidPrice, bestStaticBidVolume, _ := m.getBestStaticBidPriceAndVolume()
   788  	bestStaticOfferPrice, bestStaticOfferVolume, _ := m.getBestStaticAskPriceAndVolume()
   789  
   790  	// Auction related values
   791  	indicativePrice := num.UintZero()
   792  	indicativeVolume := uint64(0)
   793  	var auctionStart, auctionEnd int64
   794  	if m.as.InAuction() {
   795  		indicativePrice, indicativeVolume, _ = m.matching.GetIndicativePriceAndVolume()
   796  		if t := m.as.Start(); !t.IsZero() {
   797  			auctionStart = t.UnixNano()
   798  		}
   799  		if t := m.as.ExpiresAt(); t != nil {
   800  			auctionEnd = t.UnixNano()
   801  		}
   802  	}
   803  
   804  	// If we do not have one of the best_* prices, leave the mid price as zero
   805  	two := num.NewUint(2)
   806  	midPrice := num.UintZero()
   807  	if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() {
   808  		midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two)
   809  	}
   810  
   811  	staticMidPrice := num.UintZero()
   812  	if !bestStaticBidPrice.IsZero() && !bestStaticOfferPrice.IsZero() {
   813  		staticMidPrice = staticMidPrice.Div(num.Sum(bestStaticBidPrice, bestStaticOfferPrice), two)
   814  	}
   815  
   816  	var targetStake string
   817  	if m.as.InAuction() {
   818  		targetStake = m.getTheoreticalTargetStake().String()
   819  	} else {
   820  		targetStake = m.getTargetStake().String()
   821  	}
   822  	bounds := m.pMonitor.GetBounds()
   823  	for _, b := range bounds {
   824  		b.MaxValidPrice = m.priceToMarketPrecision(b.MaxValidPrice) // effictively floors this
   825  		b.MinValidPrice = m.priceToMarketPrecision(b.MinValidPrice)
   826  
   827  		rp, _ := num.UintFromDecimal(b.ReferencePrice)
   828  		rp = m.priceToMarketPrecision(rp)
   829  		b.ReferencePrice = num.DecimalFromUint(rp)
   830  
   831  		if m.priceFactor.GreaterThan(num.DecimalOne()) {
   832  			b.MinValidPrice.AddSum(common.One) // ceil
   833  		}
   834  	}
   835  	mode := m.as.Mode()
   836  	if m.mkt.TradingMode == types.MarketTradingModeNoTrading {
   837  		mode = m.mkt.TradingMode
   838  	}
   839  
   840  	var internalCompositePrice *num.Uint
   841  	var nextInternalCompositePriceCalc int64
   842  	var internalCompositePriceType vega.CompositePriceType
   843  	var internalCompositePriceState *types.CompositePriceState
   844  	pd := m.tradableInstrument.Instrument.Product.GetData(m.timeService.GetTimeNow().UnixNano())
   845  	if m.perp && pd != nil {
   846  		if m.internalCompositePriceCalculator != nil {
   847  			internalCompositePriceState = m.internalCompositePriceCalculator.GetData()
   848  			internalCompositePriceType = m.internalCompositePriceCalculator.GetConfig().CompositePriceType
   849  			internalCompositePrice = m.internalCompositePriceCalculator.GetPrice()
   850  			if internalCompositePrice == nil {
   851  				internalCompositePrice = num.UintZero()
   852  			} else {
   853  				internalCompositePrice = m.priceToMarketPrecision(internalCompositePrice)
   854  			}
   855  			nextInternalCompositePriceCalc = m.nextInternalCompositePriceCalc.UnixNano()
   856  		} else {
   857  			internalCompositePriceState = m.markPriceCalculator.GetData()
   858  			internalCompositePriceType = m.markPriceCalculator.GetConfig().CompositePriceType
   859  			internalCompositePrice = m.priceToMarketPrecision(m.getCurrentMarkPrice())
   860  			nextInternalCompositePriceCalc = m.nextMTM.UnixNano()
   861  		}
   862  		perpData := pd.Data.(*types.PerpetualData)
   863  		perpData.InternalCompositePrice = internalCompositePrice
   864  		perpData.NextInternalCompositePriceCalc = nextInternalCompositePriceCalc
   865  		perpData.InternalCompositePriceType = internalCompositePriceType
   866  		perpData.InternalCompositePriceState = internalCompositePriceState
   867  	}
   868  
   869  	md := types.MarketData{
   870  		Market:                    m.GetID(),
   871  		BestBidPrice:              m.priceToMarketPrecision(bestBidPrice),
   872  		BestBidVolume:             bestBidVolume,
   873  		BestOfferPrice:            m.priceToMarketPrecision(bestOfferPrice),
   874  		BestOfferVolume:           bestOfferVolume,
   875  		BestStaticBidPrice:        m.priceToMarketPrecision(bestStaticBidPrice),
   876  		BestStaticBidVolume:       bestStaticBidVolume,
   877  		BestStaticOfferPrice:      m.priceToMarketPrecision(bestStaticOfferPrice),
   878  		BestStaticOfferVolume:     bestStaticOfferVolume,
   879  		MidPrice:                  m.priceToMarketPrecision(midPrice),
   880  		StaticMidPrice:            m.priceToMarketPrecision(staticMidPrice),
   881  		MarkPrice:                 m.priceToMarketPrecision(m.getCurrentMarkPrice()),
   882  		LastTradedPrice:           m.priceToMarketPrecision(m.getLastTradedPrice()),
   883  		Timestamp:                 m.timeService.GetTimeNow().UnixNano(),
   884  		OpenInterest:              m.position.GetOpenInterest(),
   885  		IndicativePrice:           m.priceToMarketPrecision(indicativePrice),
   886  		IndicativeVolume:          indicativeVolume,
   887  		AuctionStart:              auctionStart,
   888  		AuctionEnd:                auctionEnd,
   889  		MarketTradingMode:         mode,
   890  		MarketState:               m.mkt.State,
   891  		Trigger:                   m.as.Trigger(),
   892  		ExtensionTrigger:          m.as.ExtensionTrigger(),
   893  		TargetStake:               targetStake,
   894  		SuppliedStake:             m.getSuppliedStake().String(),
   895  		PriceMonitoringBounds:     bounds,
   896  		MarketValueProxy:          m.lastMarketValueProxy.BigInt().String(),
   897  		LiquidityProviderFeeShare: m.equityShares.LpsToLiquidityProviderFeeShare(m.liquidityEngine.GetAverageLiquidityScores()),
   898  		LiquidityProviderSLA:      m.liquidityEngine.LiquidityProviderSLAStats(m.timeService.GetTimeNow()),
   899  		NextMTM:                   m.nextMTM.UnixNano(),
   900  		MarketGrowth:              m.equityShares.GetMarketGrowth(),
   901  		ProductData:               pd,
   902  		NextNetClose:              m.liquidation.GetNextCloseoutTS(),
   903  		MarkPriceType:             m.markPriceCalculator.GetConfig().CompositePriceType,
   904  		MarkPriceState:            m.markPriceCalculator.GetData(),
   905  	}
   906  	return md
   907  }
   908  
   909  // ReloadConf will trigger a reload of all the config settings in the market and all underlying engines
   910  // this is required when hot-reloading any config changes, eg. logger level.
   911  func (m *Market) ReloadConf(
   912  	matchingConfig matching.Config,
   913  	riskConfig risk.Config,
   914  	positionConfig positions.Config,
   915  	settlementConfig settlement.Config,
   916  	feeConfig fee.Config,
   917  ) {
   918  	m.log.Info("reloading configuration")
   919  	m.matching.ReloadConf(matchingConfig)
   920  	m.risk.ReloadConf(riskConfig)
   921  	m.position.ReloadConf(positionConfig)
   922  	m.settlement.ReloadConf(settlementConfig)
   923  	m.fee.ReloadConf(feeConfig)
   924  }
   925  
   926  func (m *Market) Reject(ctx context.Context) error {
   927  	if !m.canReject() {
   928  		return common.ErrCannotRejectMarketNotInProposedState
   929  	}
   930  
   931  	// we closed all parties accounts
   932  	m.cleanupOnReject(ctx)
   933  	m.mkt.State = types.MarketStateRejected
   934  	m.mkt.TradingMode = types.MarketTradingModeNoTrading
   935  	m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
   936  
   937  	return nil
   938  }
   939  
   940  func (m *Market) canReject() bool {
   941  	if m.mkt.State == types.MarketStateProposed {
   942  		return true
   943  	}
   944  	if len(m.mkt.ParentMarketID) == 0 {
   945  		return false
   946  	}
   947  	// parent market is set, market can be in pending state when it is rejected.
   948  	return m.mkt.State == types.MarketStatePending
   949  }
   950  
   951  func (m *Market) onTxProcessed() {
   952  	m.risk.FlushMarginLevelsEvents()
   953  }
   954  
   955  // CanLeaveOpeningAuction checks if the market can leave the opening auction based on whether floating point consensus has been reached on all 3 vars.
   956  func (m *Market) CanLeaveOpeningAuction() bool {
   957  	boundFactorsInitialised := m.pMonitor.IsBoundFactorsInitialised()
   958  	potInitialised := m.liquidity.IsProbabilityOfTradingInitialised()
   959  	riskFactorsInitialised := m.risk.IsRiskFactorInitialised()
   960  	canLeave := boundFactorsInitialised && riskFactorsInitialised && potInitialised
   961  	if !canLeave {
   962  		m.log.Info("Cannot leave opening auction", logging.String("market", m.mkt.ID), logging.Bool("bound-factors-initialised", boundFactorsInitialised), logging.Bool("risk-factors-initialised", riskFactorsInitialised))
   963  	}
   964  	return canLeave
   965  }
   966  
   967  func (m *Market) InheritParent(ctx context.Context, pstate *types.CPMarketState) {
   968  	// parent is in opening auction, do not inherit any state
   969  	if pstate.State == types.MarketStatePending {
   970  		return
   971  	}
   972  	// add the trade value from the parent
   973  	m.feeSplitter.SetTradeValue(pstate.LastTradeValue)
   974  	m.equityShares.InheritELS(pstate.Shares)
   975  }
   976  
   977  func (m *Market) RestoreELS(ctx context.Context, pstate *types.CPMarketState) {
   978  	m.equityShares.RestoreELS(pstate.Shares)
   979  }
   980  
   981  func (m *Market) RollbackInherit(ctx context.Context) {
   982  	// the InheritParent call has to be made before checking if the market can leave opening auction
   983  	// if the market did not leave opening auction, market state needs to be resored to what it was
   984  	// before the call to InheritParent was made. Market is still in opening auction, therefore
   985  	// feeSplitter trade value is zero, and equity shares are linear stake/vstake/ELS
   986  	// do make sure this call is not made when the market is active
   987  	if m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateProposed {
   988  		m.feeSplitter.SetTradeValue(num.UintZero())
   989  		m.equityShares.RollbackParentELS()
   990  	}
   991  }
   992  
   993  func (m *Market) StartOpeningAuction(ctx context.Context) error {
   994  	if m.mkt.State != types.MarketStateProposed {
   995  		return common.ErrCannotStartOpeningAuctionForMarketNotInProposedState
   996  	}
   997  
   998  	defer m.onTxProcessed()
   999  
  1000  	// now we start the opening auction
  1001  	if m.as.AuctionStart() {
  1002  		// we are now in a pending state
  1003  		m.mkt.State = types.MarketStatePending
  1004  		// this should no longer be needed
  1005  		// m.mkt.MarketTimestamps.Pending = m.timeService.GetTimeNow().UnixNano()
  1006  		m.mkt.TradingMode = types.MarketTradingModeOpeningAuction
  1007  		m.enterAuction(ctx)
  1008  	} else {
  1009  		// TODO(): to be removed once we don't have market starting
  1010  		// without an opening auction - this is only used in unit tests
  1011  		// validation on the proposal ensures opening auction duration is always >= 1 (or whatever the min duration is)
  1012  		m.mkt.State = types.MarketStateActive
  1013  		m.mkt.TradingMode = types.MarketTradingModeContinuous
  1014  	}
  1015  
  1016  	m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1017  	return nil
  1018  }
  1019  
  1020  // GetID returns the id of the given market.
  1021  func (m *Market) GetID() string {
  1022  	return m.mkt.ID
  1023  }
  1024  
  1025  func (m *Market) PostRestore(ctx context.Context) error {
  1026  	// tell the matching engine about the markets price factor so it can finish restoring orders
  1027  	m.matching.RestoreWithMarketPriceFactor(m.priceFactor)
  1028  
  1029  	// if loading from an old snapshot we're restoring positions using the position engine
  1030  	if m.marketActivityTracker.NeedsInitialisation(m.settlementAsset, m.mkt.ID) {
  1031  		for _, mp := range m.position.Positions() {
  1032  			if mp.Size() != 0 {
  1033  				m.marketActivityTracker.RestorePosition(m.settlementAsset, mp.Party(), m.mkt.ID, mp.Size(), mp.Price(), m.positionFactor)
  1034  			}
  1035  		}
  1036  	}
  1037  	m.marketActivityTracker.RestoreMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())
  1038  
  1039  	// Disposal slippage was set as part of this upgrade, send event to ensure datanode is updated.
  1040  	if vegacontext.InProgressUpgradeFromMultiple(ctx, "v0.75.8", "v0.75.7") {
  1041  		m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1042  	}
  1043  
  1044  	return nil
  1045  }
  1046  
  1047  // OnTick notifies the market of a new time event/update.
  1048  // todo: make this a more generic function name e.g. OnTimeUpdateEvent
  1049  func (m *Market) OnTick(ctx context.Context, t time.Time) bool {
  1050  	defer m.onTxProcessed()
  1051  
  1052  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "OnTick")
  1053  	m.mu.Lock()
  1054  	defer m.mu.Unlock()
  1055  
  1056  	_, blockHash := vegacontext.TraceIDFromContext(ctx)
  1057  	// make deterministic ID for this market, concatenate
  1058  	// the block hash and the market ID
  1059  	m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID()))
  1060  	// and we call next ID on this directly just so we don't have an ID which have
  1061  	// a different from others, we basically burn the first ID.
  1062  	_ = m.idgen.NextID()
  1063  	defer func() { m.idgen = nil }()
  1064  
  1065  	if m.closed {
  1066  		return true
  1067  	}
  1068  
  1069  	// first we check if we should reduce the network position, then we expire orders
  1070  	if !m.closed && m.canTrade() {
  1071  		m.checkNetwork(ctx, t)
  1072  		expired := m.removeExpiredOrders(ctx, t.UnixNano())
  1073  		metrics.OrderGaugeAdd(-len(expired), m.GetID())
  1074  		confirmations := m.removeExpiredStopOrders(ctx, t.UnixNano(), m.idgen)
  1075  
  1076  		stopsExpired := 0
  1077  		for _, v := range confirmations {
  1078  			stopsExpired++
  1079  			for _, v := range v.PassiveOrdersAffected {
  1080  				if v.Status != types.OrderStatusActive {
  1081  					stopsExpired++
  1082  				}
  1083  			}
  1084  		}
  1085  		metrics.OrderGaugeAdd(-stopsExpired, m.GetID())
  1086  	}
  1087  
  1088  	// some engines still needs to get updates:
  1089  	m.pMonitor.OnTimeUpdate(t)
  1090  	m.feeSplitter.SetCurrentTime(t)
  1091  
  1092  	// TODO(): This also assume that the market is not
  1093  	// being closed before the market is leaving
  1094  	// the opening auction, but settlement at expiry is
  1095  	// not even specced or implemented as of now...
  1096  	// if the state of the market is just PROPOSED,
  1097  	// we will just skip everything there as nothing apply.
  1098  	if m.mkt.State == types.MarketStateProposed {
  1099  		return false
  1100  	}
  1101  
  1102  	// if trading is terminated, we have nothing to do here.
  1103  	// we just need to wait for the settlementData to arrive through oracle
  1104  	if m.mkt.State == types.MarketStateTradingTerminated {
  1105  		return false
  1106  	}
  1107  
  1108  	m.liquidity.OnTick(ctx, t)
  1109  	m.amm.OnTick(ctx, t)
  1110  
  1111  	// check auction, if any. If we leave auction, MTM is performed in this call
  1112  	m.checkAuction(ctx, t, m.idgen)
  1113  	// check the position of the network, may place orders to close the network out
  1114  	timer.EngineTimeCounterAdd()
  1115  
  1116  	m.updateMarketValueProxy()
  1117  	m.updateLiquidityFee(ctx)
  1118  	m.broker.Send(events.NewMarketTick(ctx, m.mkt.ID, t))
  1119  	return m.closed
  1120  }
  1121  
  1122  // BlockEnd notifies the market of the end of the block.
  1123  func (m *Market) BlockEnd(ctx context.Context) {
  1124  	defer m.onTxProcessed()
  1125  	t := m.timeService.GetTimeNow()
  1126  	if m.mkt.State == types.MarketStateProposed ||
  1127  		m.mkt.State == types.MarketStateCancelled ||
  1128  		m.mkt.State == types.MarketStateRejected ||
  1129  		m.mkt.State == types.MarketStateSettled {
  1130  		if (m.nextMTM.IsZero() || !m.nextMTM.After(t)) && !m.as.InAuction() {
  1131  			m.nextMTM = t.Add(m.mtmDelta)
  1132  		}
  1133  		return
  1134  	}
  1135  
  1136  	// MTM if enough time has elapsed, we are not in auction, and we have a non-zero mark price.
  1137  	// we MTM in leaveAuction before deploying LP orders like we did before, but we do update nextMTM there
  1138  	var tID string
  1139  	ctx, tID = vegacontext.TraceIDFromContext(ctx)
  1140  	m.idgen = idgeneration.New(tID + crypto.HashStrToHex("blockend"+m.GetID()))
  1141  	defer func() {
  1142  		m.idgen = nil
  1143  	}()
  1144  
  1145  	if !m.as.InAuction() {
  1146  		m.markPriceCalculator.CalculateBookMarkPriceAtTimeT(m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, t.UnixNano(), m.matching)
  1147  		if m.internalCompositePriceCalculator != nil {
  1148  			m.internalCompositePriceCalculator.CalculateBookMarkPriceAtTimeT(m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, t.UnixNano(), m.matching)
  1149  		}
  1150  	}
  1151  
  1152  	// if we do have a separate configuration for the intenal composite price and we have a new intenal composite price we push it to the perp
  1153  	if m.internalCompositePriceCalculator != nil && (m.nextInternalCompositePriceCalc.IsZero() ||
  1154  		!m.nextInternalCompositePriceCalc.After(t) &&
  1155  			!m.as.InAuction()) {
  1156  		prevInternalCompositePrice := m.internalCompositePriceCalculator.GetPrice()
  1157  		m.internalCompositePriceCalculator.CalculateMarkPrice(
  1158  			ctx,
  1159  			m.pMonitor,
  1160  			m.as,
  1161  			t.UnixNano(),
  1162  			m.matching,
  1163  			m.mtmDelta,
  1164  			m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, false, false)
  1165  		m.nextInternalCompositePriceCalc = t.Add(m.internalCompositePriceFrequency)
  1166  		if (prevInternalCompositePrice == nil || !m.internalCompositePriceCalculator.GetPrice().EQ(prevInternalCompositePrice) || m.settlement.HasTraded()) &&
  1167  			!m.getCurrentInternalCompositePrice().IsZero() {
  1168  			m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentInternalCompositePrice(), m.timeService.GetTimeNow().UnixNano())
  1169  		}
  1170  	}
  1171  
  1172  	// if it's time for mtm, let's do it
  1173  	if (m.nextMTM.IsZero() || !m.nextMTM.After(t)) && !m.as.InAuction() {
  1174  		prevMarkPrice := m.markPriceCalculator.GetPrice()
  1175  		m.markPriceLock.Lock()
  1176  		_, err := m.markPriceCalculator.CalculateMarkPrice(
  1177  			ctx,
  1178  			m.pMonitor,
  1179  			m.as,
  1180  			t.UnixNano(),
  1181  			m.matching,
  1182  			m.mtmDelta,
  1183  			m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin,
  1184  			m.mkt.LinearSlippageFactor,
  1185  			m.risk.GetRiskFactors().Short,
  1186  			m.risk.GetRiskFactors().Long,
  1187  			true, false)
  1188  		m.markPriceLock.Unlock()
  1189  		m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())
  1190  		if err != nil {
  1191  			// start the  monitoring auction if required
  1192  			if m.as.AuctionStart() {
  1193  				m.enterAuction(ctx)
  1194  			}
  1195  		} else {
  1196  			// if we don't have an alternative configuration (and schedule) for the mark price the we push the mark price to the perp as a new datapoint
  1197  			// on the standard mark price
  1198  			if m.internalCompositePriceCalculator == nil && m.perp &&
  1199  				(prevMarkPrice == nil || !m.markPriceCalculator.GetPrice().EQ(prevMarkPrice) || m.settlement.HasTraded()) &&
  1200  				!m.getCurrentMarkPrice().IsZero() {
  1201  				m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentMarkPrice(), m.timeService.GetTimeNow().UnixNano())
  1202  			}
  1203  		}
  1204  		m.nextMTM = t.Add(m.mtmDelta)
  1205  
  1206  		// mark price mustn't be zero, except for capped futures, where a zero price may well be possible
  1207  		if !m.as.InAuction() && (prevMarkPrice == nil || !m.markPriceCalculator.GetPrice().EQ(prevMarkPrice) || m.settlement.HasTraded()) &&
  1208  			(!m.getCurrentMarkPrice().IsZero() || m.capMax != nil) {
  1209  			if m.confirmMTM(ctx, false) {
  1210  				closedPositions := m.position.GetClosedPositions()
  1211  				if len(closedPositions) > 0 {
  1212  					m.releaseExcessMargin(ctx, closedPositions...)
  1213  					// also remove all stop orders
  1214  					m.removeAllStopOrders(ctx, closedPositions...)
  1215  				}
  1216  			} else {
  1217  				// do not increment the next MTM time to now + delta
  1218  				// this ensures we try to perform the MTM settlement ASAP.
  1219  				m.nextMTM = t
  1220  			}
  1221  		}
  1222  	}
  1223  
  1224  	m.releaseExcessMargin(ctx, m.position.Positions()...)
  1225  	// send position events
  1226  	m.position.FlushPositionEvents(ctx)
  1227  
  1228  	var markPriceCopy *num.Uint
  1229  	if m.markPriceCalculator.GetPrice() != nil {
  1230  		markPriceCopy = m.markPriceCalculator.GetPrice().Clone()
  1231  	}
  1232  	m.liquidity.EndBlock(markPriceCopy, m.midPrice(), m.positionFactor)
  1233  
  1234  	if !m.matching.CheckBook() {
  1235  		m.log.Panic("ontick book has orders pegged to nothing")
  1236  	}
  1237  }
  1238  
  1239  func (m *Market) removeAllStopOrders(
  1240  	ctx context.Context,
  1241  	positions ...events.MarketPosition,
  1242  ) {
  1243  	evts := []events.Event{}
  1244  
  1245  	for _, v := range positions {
  1246  		sos, _ := m.stopOrders.Cancel(v.Party(), "")
  1247  		for _, so := range sos {
  1248  			if so.Expiry.Expires() {
  1249  				_ = m.expiringStopOrders.RemoveOrder(so.Expiry.ExpiresAt.UnixNano(), so.ID)
  1250  			}
  1251  			evts = append(evts, events.NewStopOrderEvent(ctx, so))
  1252  		}
  1253  	}
  1254  
  1255  	if len(evts) > 0 {
  1256  		m.broker.SendBatch(evts)
  1257  	}
  1258  }
  1259  
  1260  func (m *Market) updateMarketValueProxy() {
  1261  	// if windows length is reached, reset fee splitter
  1262  	if mvwl := m.marketValueWindowLength; m.feeSplitter.Elapsed() > mvwl {
  1263  		// AvgTradeValue calculates the rolling average trade value to include the current window (which is ending)
  1264  		m.equityShares.AvgTradeValue(m.feeSplitter.AvgTradeValue())
  1265  		// this increments the internal window counter
  1266  		m.feeSplitter.TimeWindowStart(m.timeService.GetTimeNow())
  1267  		// m.equityShares.UpdateVirtualStake() // this should always set the vStake >= physical stake?
  1268  	}
  1269  
  1270  	// TODO karel - do we still need to calculate the market value proxy????
  1271  	// these need to happen every block
  1272  	// but also when new LP is submitted just so we are sure we do
  1273  	// not have a mvp of 0
  1274  	// ts := m.liquidity.Stake
  1275  	// m.lastMarketValueProxy = m.feeSplitter.MarketValueProxy(
  1276  	// 	m.marketValueWindowLength, ts)
  1277  }
  1278  
  1279  func (m *Market) removeOrders(ctx context.Context) {
  1280  	// remove all order from the book
  1281  	// and send events with the stopped status
  1282  	orders := append(m.matching.Settled(), m.peggedOrders.Settled()...)
  1283  	orderEvents := make([]events.Event, 0, len(orders))
  1284  	for _, v := range orders {
  1285  		orderEvents = append(orderEvents, events.NewOrderEvent(ctx, v))
  1286  	}
  1287  
  1288  	m.broker.SendBatch(orderEvents)
  1289  }
  1290  
  1291  func (m *Market) cleanMarketWithState(ctx context.Context, mktState types.MarketState) error {
  1292  	parties := make([]string, 0, len(m.parties))
  1293  	for k := range m.parties {
  1294  		parties = append(parties, k)
  1295  	}
  1296  
  1297  	// insurance pool has to be preserved in case a successor market leaves opening auction
  1298  	// the insurance pool must be preserved if a market is settled or was closed through governance
  1299  	keepInsurance := (mktState == types.MarketStateSettled || mktState == types.MarketStateClosed) && !m.succeeded
  1300  	sort.Strings(parties)
  1301  	clearMarketTransfers, err := m.collateral.ClearMarket(ctx, m.GetID(), m.settlementAsset, parties, keepInsurance)
  1302  	if err != nil {
  1303  		m.log.Error("Clear market error",
  1304  			logging.MarketID(m.GetID()),
  1305  			logging.Error(err))
  1306  		return err
  1307  	}
  1308  
  1309  	// unregister state-variables
  1310  	m.stateVarEngine.UnregisterStateVariable(m.settlementAsset, m.mkt.ID)
  1311  
  1312  	if len(clearMarketTransfers) > 0 {
  1313  		m.broker.Send(events.NewLedgerMovements(ctx, clearMarketTransfers))
  1314  	}
  1315  
  1316  	m.markPriceCalculator.Close(ctx)
  1317  	if m.internalCompositePriceCalculator != nil {
  1318  		m.internalCompositePriceCalculator.Close(ctx)
  1319  	}
  1320  
  1321  	if err := m.amm.MarketClosing(ctx); err != nil {
  1322  		return err
  1323  	}
  1324  
  1325  	m.mkt.State = mktState
  1326  	m.mkt.TradingMode = types.MarketTradingModeNoTrading
  1327  	m.mkt.MarketTimestamps.Close = m.timeService.GetTimeNow().UnixNano()
  1328  	m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1329  
  1330  	return nil
  1331  }
  1332  
  1333  func (m *Market) closeCancelledMarket(ctx context.Context) error {
  1334  	// we got here because trading was terminated, so we've already unsubscribed that oracle data source.
  1335  	m.tradableInstrument.Instrument.UnsubscribeSettlementData(ctx)
  1336  
  1337  	if err := m.cleanMarketWithState(ctx, types.MarketStateCancelled); err != nil {
  1338  		return err
  1339  	}
  1340  
  1341  	m.liquidity.StopAllLiquidityProvision(ctx)
  1342  
  1343  	m.closed = true
  1344  
  1345  	return nil
  1346  }
  1347  
  1348  func (m *Market) recordPositionActivity(t *types.Transfer) {
  1349  	if t == nil || t.Amount == nil || t.Amount.Amount == nil {
  1350  		return
  1351  	}
  1352  	amt := t.Amount.Amount.ToDecimal()
  1353  	if t.Type == types.TransferTypeMTMLoss || t.Type == types.TransferTypePerpFundingLoss {
  1354  		amt = t.Amount.Amount.ToDecimal().Mul(num.DecimalMinusOne())
  1355  	}
  1356  	if t.Type == types.TransferTypeMTMWin || t.Type == types.TransferTypeMTMLoss ||
  1357  		t.Type == types.TransferTypePerpFundingWin || t.Type == types.TransferTypePerpFundingLoss {
  1358  		m.marketActivityTracker.RecordM2M(m.settlementAsset, t.Owner, m.mkt.ID, amt.Div(m.assetFactor))
  1359  	}
  1360  	if t.Type == types.TransferTypePerpFundingWin || t.Type == types.TransferTypePerpFundingLoss {
  1361  		m.marketActivityTracker.RecordFundingPayment(m.settlementAsset, t.Owner, m.mkt.ID, amt)
  1362  	}
  1363  }
  1364  
  1365  func (m *Market) closeMarket(ctx context.Context, t time.Time, finalState types.MarketState, settlementPriceInAsset *num.Uint) error {
  1366  	positions, round, err := m.settlement.Settle(t, settlementPriceInAsset)
  1367  	if err != nil {
  1368  		m.log.Error("Failed to get settle positions on market closed",
  1369  			logging.Error(err))
  1370  
  1371  		return err
  1372  	}
  1373  
  1374  	for _, t := range positions {
  1375  		m.recordPositionActivity(t)
  1376  	}
  1377  
  1378  	transfers, err := m.collateral.FinalSettlement(ctx, m.GetID(), positions, round, m.useGeneralAccountForMarginSearch)
  1379  	if err != nil {
  1380  		m.log.Error("Failed to get ledger movements after settling closed market",
  1381  			logging.MarketID(m.GetID()),
  1382  			logging.Error(err))
  1383  		return err
  1384  	}
  1385  
  1386  	m.tradableInstrument.Instrument.UnsubscribeSettlementData(ctx)
  1387  	// @TODO pass in correct context -> Previous or next block?
  1388  	// Which is most appropriate here?
  1389  	// this will be next block
  1390  	if len(transfers) > 0 {
  1391  		m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  1392  	}
  1393  
  1394  	// final distribution of liquidity fees
  1395  	if !m.finalFeesDistributed {
  1396  		if err := m.liquidity.AllocateFees(ctx); err != nil {
  1397  			m.log.Panic("failed to allocate liquidity provision fees", logging.Error(err))
  1398  		}
  1399  
  1400  		m.liquidity.OnEpochEnd(ctx, t, m.epoch)
  1401  		m.finalFeesDistributed = true
  1402  	}
  1403  
  1404  	err = m.cleanMarketWithState(ctx, finalState)
  1405  	if err != nil {
  1406  		return err
  1407  	}
  1408  
  1409  	m.removeOrders(ctx)
  1410  
  1411  	m.liquidity.StopAllLiquidityProvision(ctx)
  1412  
  1413  	return nil
  1414  }
  1415  
  1416  func (m *Market) unregisterAndReject(ctx context.Context, order *types.Order, err error) error {
  1417  	// in case the order was reduce only
  1418  	order.ClearUpExtraRemaining()
  1419  
  1420  	_ = m.position.UnregisterOrder(ctx, order)
  1421  	order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
  1422  	order.Status = types.OrderStatusRejected
  1423  	if oerr, ok := types.IsOrderError(err); ok {
  1424  		// the order wasn't invalid, so stopped is a better status, rather than rejected.
  1425  		if types.IsStoppingOrder(oerr) {
  1426  			order.Status = types.OrderStatusStopped
  1427  		}
  1428  		order.Reason = oerr
  1429  	} else {
  1430  		// should not happened but still...
  1431  		order.Reason = types.OrderErrorInternalError
  1432  	}
  1433  	m.broker.Send(events.NewOrderEvent(ctx, order))
  1434  	if m.log.GetLevel() == logging.DebugLevel {
  1435  		m.log.Debug("Failure after submitting order to matching engine",
  1436  			logging.Order(*order),
  1437  			logging.Error(err))
  1438  	}
  1439  	return err
  1440  }
  1441  
  1442  func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) {
  1443  	if m.as.InAuction() {
  1444  		return num.UintZero(), common.ErrCannotRepriceDuringAuction
  1445  	}
  1446  
  1447  	var (
  1448  		err   error
  1449  		price *num.Uint
  1450  	)
  1451  
  1452  	switch order.PeggedOrder.Reference {
  1453  	case types.PeggedReferenceMid:
  1454  		price, err = m.getStaticMidPrice(order.Side)
  1455  	case types.PeggedReferenceBestBid:
  1456  		price, err = m.getBestStaticBidPrice()
  1457  	case types.PeggedReferenceBestAsk:
  1458  		price, err = m.getBestStaticAskPrice()
  1459  	}
  1460  	if err != nil {
  1461  		return num.UintZero(), common.ErrUnableToReprice
  1462  	}
  1463  
  1464  	// we're converting both offset and tick size to asset decimals so we can adjust the price (in asset) directly
  1465  	inMarketD := price.ToDecimal().Div(m.priceFactor)
  1466  
  1467  	// if there are any decimals (because its an AMM price) then rounding down on a sell with 0 offset could cause a crossed book if the spread is small
  1468  	if order.PeggedOrder.Reference == types.PeggedReferenceBestAsk {
  1469  		inMarketD = inMarketD.Ceil()
  1470  	}
  1471  	priceInMarket, _ := num.UintFromDecimal(inMarketD)
  1472  
  1473  	// if the pegged offset is zero and the reference price is non-tick size (from an AMM) then we have to move it so it
  1474  	// is otherwise the pegged will cross.
  1475  	if order.PeggedOrder.Offset.IsZero() {
  1476  		if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
  1477  			if order.Side == types.SideBuy {
  1478  				priceInMarket.Sub(priceInMarket, mod)
  1479  			} else {
  1480  				d := num.UintOne().Sub(m.mkt.TickSize, mod)
  1481  				priceInMarket.AddSum(d)
  1482  			}
  1483  		}
  1484  	}
  1485  
  1486  	if order.Side == types.SideSell {
  1487  		priceInMarket.AddSum(order.PeggedOrder.Offset)
  1488  
  1489  		// this can only happen when pegged to mid, in which case we want to round to the nearest *better* tick size
  1490  		// but this can never cross the mid by construction as the the minimum offset is 1 tick size and all prices must be
  1491  		// whole multiples of tick size.
  1492  		if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
  1493  			priceInMarket.Sub(priceInMarket, mod)
  1494  		}
  1495  		price, _ := num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
  1496  
  1497  		if m.capMax != nil {
  1498  			upper := num.UintZero().Sub(m.capMax, num.UintOne())
  1499  			price = num.Min(price, upper)
  1500  		}
  1501  		if price.IsZero() {
  1502  			price = num.UintOne()
  1503  		}
  1504  		return price, nil
  1505  	}
  1506  
  1507  	if priceInMarket.LTE(order.PeggedOrder.Offset) {
  1508  		return num.UintZero(), common.ErrUnableToReprice
  1509  	}
  1510  
  1511  	priceInMarket.Sub(priceInMarket, order.PeggedOrder.Offset)
  1512  	if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
  1513  		priceInMarket = num.UintZero().Sub(priceInMarket.AddSum(m.mkt.TickSize), mod)
  1514  	}
  1515  	price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
  1516  
  1517  	if m.capMax != nil {
  1518  		upper := num.UintZero().Sub(m.capMax, num.UintOne())
  1519  		price = num.Min(price, upper)
  1520  	}
  1521  	if price.IsZero() {
  1522  		price = num.UintOne()
  1523  	}
  1524  	return price, nil
  1525  }
  1526  
  1527  // Reprice a pegged order. This only updates the price on the order.
  1528  func (m *Market) repricePeggedOrder(order *types.Order) error {
  1529  	// Work out the new price of the order
  1530  	price, err := m.getNewPeggedPrice(order)
  1531  	if err != nil {
  1532  		return err
  1533  	}
  1534  	order.OriginalPrice, _ = num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) // set original price in market precision
  1535  	order.Price = price
  1536  	return nil
  1537  }
  1538  
  1539  func (m *Market) parkAllPeggedOrders(ctx context.Context) []*types.Order {
  1540  	toParkIDs := m.matching.GetActivePeggedOrderIDs()
  1541  
  1542  	parked := make([]*types.Order, 0, len(toParkIDs))
  1543  	for _, order := range toParkIDs {
  1544  		parked = append(parked, m.parkOrder(ctx, order))
  1545  	}
  1546  	return parked
  1547  }
  1548  
  1549  func (m *Market) uncrossOrderAtAuctionEnd(ctx context.Context) {
  1550  	if !m.as.InAuction() || m.as.IsOpeningAuction() {
  1551  		return
  1552  	}
  1553  	m.uncrossOnLeaveAuction(ctx)
  1554  }
  1555  
  1556  func (m *Market) EnterLongBlockAuction(ctx context.Context, duration int64) {
  1557  	if !m.canTrade() {
  1558  		return
  1559  	}
  1560  	// markets that are suspended via governance should be unaffected by long block auctions.
  1561  	if m.mkt.TradingMode == types.MarketTradingModeSuspendedViaGovernance {
  1562  		return
  1563  	}
  1564  
  1565  	if m.as.InAuction() {
  1566  		// auction remaining:
  1567  		now := m.timeService.GetTimeNow()
  1568  		aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second)
  1569  		if aRemaining >= duration {
  1570  			return
  1571  		}
  1572  		m.as.ExtendAuctionLongBlock(types.AuctionDuration{
  1573  			Duration: duration - aRemaining,
  1574  		})
  1575  		if evt := m.as.AuctionExtended(ctx, now); evt != nil {
  1576  			m.broker.Send(evt)
  1577  		}
  1578  	} else {
  1579  		m.as.StartLongBlockAuction(m.timeService.GetTimeNow(), duration)
  1580  		m.tradableInstrument.Instrument.UpdateAuctionState(ctx, true)
  1581  		m.mkt.TradingMode = types.MarketTradingModeLongBlockAuction
  1582  		m.mkt.State = types.MarketStateSuspended
  1583  		m.enterAuction(ctx)
  1584  		m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1585  	}
  1586  }
  1587  
  1588  func (m *Market) UpdateMarketState(ctx context.Context, changes *types.MarketStateUpdateConfiguration) error {
  1589  	_, blockHash := vegacontext.TraceIDFromContext(ctx)
  1590  	// make deterministic ID for this market, concatenate
  1591  	// the block hash and the market ID
  1592  	m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID()))
  1593  	// and we call next ID on this directly just so we don't have an ID which have
  1594  	// a different from others, we basically burn the first ID.
  1595  	_ = m.idgen.NextID()
  1596  	defer func() { m.idgen = nil }()
  1597  	if changes.UpdateType == types.MarketStateUpdateTypeTerminate {
  1598  		final := types.MarketStateClosed
  1599  		if m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateProposed {
  1600  			final = types.MarketStateCancelled
  1601  		}
  1602  		// terminate and settle data (either last traded price for perp, or settlement data provided via governance
  1603  		settlement, _ := num.UintFromDecimal(changes.SettlementPrice.ToDecimal().Mul(m.priceFactor))
  1604  		if !m.validateSettlementData(settlement) {
  1605  			// final settlement is not valid/impossible
  1606  			return common.ErrSettlementDataOutOfRange
  1607  		}
  1608  		// in case we're in auction, uncross
  1609  		m.uncrossOrderAtAuctionEnd(ctx)
  1610  		m.tradingTerminatedWithFinalState(ctx, final, settlement)
  1611  	} else if changes.UpdateType == types.MarketStateUpdateTypeSuspend {
  1612  		m.mkt.State = types.MarketStateSuspendedViaGovernance
  1613  		m.mkt.TradingMode = types.MarketTradingModeSuspendedViaGovernance
  1614  		if m.as.InAuction() {
  1615  			m.as.ExtendAuctionSuspension(types.AuctionDuration{Duration: int64(m.minDuration.Seconds())})
  1616  			evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow())
  1617  			if evt != nil {
  1618  				m.broker.Send(evt)
  1619  			}
  1620  		} else {
  1621  			m.as.StartGovernanceSuspensionAuction(m.timeService.GetTimeNow())
  1622  			m.tradableInstrument.Instrument.UpdateAuctionState(ctx, true)
  1623  			m.enterAuction(ctx)
  1624  			m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1625  		}
  1626  	} else if changes.UpdateType == types.MarketStateUpdateTypeResume && m.mkt.State == types.MarketStateSuspendedViaGovernance {
  1627  		if m.as.GetState().Trigger == types.AuctionTriggerGovernanceSuspension && m.as.GetState().Extension == types.AuctionTriggerUnspecified {
  1628  			m.as.EndGovernanceSuspensionAuction()
  1629  			m.leaveAuction(ctx, m.timeService.GetTimeNow())
  1630  		} else {
  1631  			m.as.EndGovernanceSuspensionAuction()
  1632  			if m.as.GetState().Trigger == types.AuctionTriggerOpening {
  1633  				m.mkt.State = types.MarketStatePending
  1634  				m.mkt.TradingMode = types.MarketTradingModeOpeningAuction
  1635  			} else {
  1636  				m.mkt.State = types.MarketStateSuspended
  1637  				m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction
  1638  			}
  1639  			m.checkAuction(ctx, m.timeService.GetTimeNow(), m.idgen)
  1640  			m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1641  		}
  1642  	}
  1643  	return nil
  1644  }
  1645  
  1646  // EnterAuction : Prepare the order book to be run as an auction.
  1647  func (m *Market) enterAuction(ctx context.Context) {
  1648  	// Change market type to auction
  1649  	ordersToCancel := m.matching.EnterAuction()
  1650  
  1651  	// Move into auction mode to prevent pegged order repricing
  1652  	event := m.as.AuctionStarted(ctx, m.timeService.GetTimeNow())
  1653  
  1654  	// Cancel all the orders that were invalid
  1655  	for _, order := range ordersToCancel {
  1656  		_, err := m.cancelOrder(ctx, order.Party, order.ID)
  1657  		if err != nil {
  1658  			m.log.Debug("error cancelling order when entering auction",
  1659  				logging.MarketID(m.GetID()),
  1660  				logging.OrderID(order.ID),
  1661  				logging.Error(err))
  1662  		}
  1663  	}
  1664  
  1665  	// now update all special orders
  1666  	m.enterAuctionSpecialOrders(ctx)
  1667  
  1668  	// Send an event bus update
  1669  	m.broker.Send(event)
  1670  
  1671  	if m.as.InAuction() && m.as.IsPriceAuction() {
  1672  		m.mkt.State = types.MarketStateSuspended
  1673  		m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction
  1674  		m.tradableInstrument.Instrument.UpdateAuctionState(ctx, true)
  1675  		m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1676  	}
  1677  }
  1678  
  1679  func (m *Market) uncrossOnLeaveAuction(ctx context.Context) ([]*types.OrderConfirmation, []*types.Order) {
  1680  	uncrossedOrders, ordersToCancel, err := m.matching.LeaveAuction(m.timeService.GetTimeNow())
  1681  	if err != nil {
  1682  		m.log.Error("Error leaving auction", logging.Error(err))
  1683  	}
  1684  
  1685  	// Process each confirmation & apply fee calculations to each trade
  1686  	evts := make([]events.Event, 0, len(uncrossedOrders))
  1687  	for _, uncrossedOrder := range uncrossedOrders {
  1688  		// handle fees first
  1689  		fees, err := m.calcFees(uncrossedOrder.Trades)
  1690  		if err != nil {
  1691  			// @TODO this ought to be an event
  1692  			m.log.Error("Unable to calculate fees to order",
  1693  				logging.String("OrderID", uncrossedOrder.Order.ID))
  1694  		} else {
  1695  			if fees != nil {
  1696  				err = m.applyFees(ctx, uncrossedOrder.Order, fees)
  1697  				if err != nil {
  1698  					// @TODO this ought to be an event
  1699  					m.log.Error("Unable to apply fees to order",
  1700  						logging.String("OrderID", uncrossedOrder.Order.ID))
  1701  				}
  1702  			}
  1703  		}
  1704  
  1705  		// if the uncrossed order was generated by an AMM then register its position as if it was submitted
  1706  		order := uncrossedOrder.Order
  1707  		if order.GeneratedOffbook {
  1708  			cpy := order.Clone()
  1709  			cpy.Remaining = cpy.Size // remaining will be 0 since it has traded, so we copy it back to its full size to register
  1710  			m.position.RegisterOrder(ctx, cpy)
  1711  		}
  1712  
  1713  		// then do the confirmation
  1714  		m.handleConfirmation(ctx, uncrossedOrder, nil)
  1715  
  1716  		if uncrossedOrder.Order.Remaining == 0 {
  1717  			uncrossedOrder.Order.Status = types.OrderStatusFilled
  1718  		}
  1719  		evts = append(evts, events.NewOrderEvent(ctx, uncrossedOrder.Order))
  1720  	}
  1721  
  1722  	// send order events in a single batch, it's more efficient
  1723  	m.broker.SendBatch(evts)
  1724  
  1725  	// after auction uncrossing we can relax the price requirement and release some excess order margin if any was placed during an auction.
  1726  	for k, d := range m.partyMarginFactor {
  1727  		partyPos, _ := m.position.GetPositionByPartyID(k)
  1728  		if partyPos != nil && (partyPos.Buy() != 0 || partyPos.Sell() != 0) {
  1729  			marketObservable, mpos, increment, _, _, orders, err := m.getIsolatedMarginContext(partyPos, nil)
  1730  			if err != nil {
  1731  				continue
  1732  			}
  1733  			r := m.risk.ReleaseExcessMarginAfterAuctionUncrossing(ctx, mpos, marketObservable, increment, d, orders)
  1734  			if r != nil && r.Transfer() != nil {
  1735  				m.transferMargins(ctx, []events.Risk{r}, nil)
  1736  			}
  1737  		}
  1738  	}
  1739  
  1740  	return uncrossedOrders, ordersToCancel
  1741  }
  1742  
  1743  // OnOpeningAuctionFirstUncrossingPrice is triggered when the opening auction sees an uncrossing price for the first time and emits
  1744  // an event to the state variable engine.
  1745  func (m *Market) OnOpeningAuctionFirstUncrossingPrice() {
  1746  	m.log.Info("OnOpeningAuctionFirstUncrossingPrice event fired", logging.String("market", m.mkt.ID))
  1747  	m.stateVarEngine.ReadyForTimeTrigger(m.settlementAsset, m.mkt.ID)
  1748  	m.stateVarEngine.NewEvent(m.settlementAsset, m.mkt.ID, statevar.EventTypeOpeningAuctionFirstUncrossingPrice)
  1749  }
  1750  
  1751  // OnAuctionEnded is called whenever an auction is ended and emits an event to the state var engine.
  1752  func (m *Market) OnAuctionEnded() {
  1753  	m.log.Info("OnAuctionEnded event fired", logging.String("market", m.mkt.ID))
  1754  	m.stateVarEngine.NewEvent(m.settlementAsset, m.mkt.ID, statevar.EventTypeAuctionEnded)
  1755  }
  1756  
  1757  // leaveAuction : Return the orderbook and market to continuous trading.
  1758  func (m *Market) leaveAuction(ctx context.Context, now time.Time) {
  1759  	defer func() {
  1760  		if !m.as.InAuction() && (m.mkt.State == types.MarketStateSuspended || m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateSuspendedViaGovernance) {
  1761  			if m.mkt.State == types.MarketStatePending {
  1762  				// the market is now properly open,
  1763  				// so set the timestamp to when the opening auction actually ended
  1764  				m.mkt.MarketTimestamps.Open = now.UnixNano()
  1765  			}
  1766  
  1767  			m.mkt.State = types.MarketStateActive
  1768  			// this probably should get the default trading mode from the market definition.
  1769  			m.mkt.TradingMode = types.MarketTradingModeContinuous
  1770  			m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1771  
  1772  			m.updateLiquidityFee(ctx)
  1773  			m.OnAuctionEnded()
  1774  		}
  1775  	}()
  1776  
  1777  	uncrossedOrders, ordersToCancel := m.uncrossOnLeaveAuction(ctx)
  1778  	// will hold all orders which have been updated by the uncrossing
  1779  	// or which were cancelled at end of auction
  1780  	updatedOrders := []*types.Order{}
  1781  
  1782  	// Process each order we have to cancel
  1783  	for _, order := range ordersToCancel {
  1784  		conf, err := m.cancelOrder(ctx, order.Party, order.ID)
  1785  		// it is possible for a party in isolated margin that their orders have been stopped when uncrossed
  1786  		// due to having insufficient order margin so we don't need to panic in this case
  1787  		if (m.getMarginMode(order.Party) == types.MarginModeCrossMargin && err == common.ErrOrderNotFound) || (err != nil && err != common.ErrOrderNotFound) {
  1788  			m.log.Panic("Failed to cancel order",
  1789  				logging.Error(err),
  1790  				logging.String("OrderID", order.ID))
  1791  		}
  1792  		if err == nil {
  1793  			updatedOrders = append(updatedOrders, conf.Order)
  1794  		}
  1795  	}
  1796  
  1797  	wasOpeningAuction := m.IsOpeningAuction()
  1798  
  1799  	// update auction state, so we know what the new tradeMode ought to be
  1800  	endEvt := m.as.Left(ctx, now)
  1801  	// we tell the perp that we've left auction, we might re-enter just a bit down but thats fine as
  1802  	// we will at least keep the in/out orders in sync
  1803  	m.tradableInstrument.Instrument.UpdateAuctionState(ctx, false)
  1804  
  1805  	for _, uncrossedOrder := range uncrossedOrders {
  1806  		updatedOrders = append(updatedOrders, uncrossedOrder.Order)
  1807  		updatedOrders = append(
  1808  			updatedOrders, uncrossedOrder.PassiveOrdersAffected...)
  1809  	}
  1810  	t := m.timeService.GetTimeNow().UnixNano()
  1811  
  1812  	m.markPriceCalculator.SetBookPriceAtTimeT(m.lastTradedPrice, t)
  1813  	m.markPriceLock.Lock()
  1814  	if _, err := m.markPriceCalculator.CalculateMarkPrice(
  1815  		ctx,
  1816  		m.pMonitor,
  1817  		m.as,
  1818  		t,
  1819  		m.matching,
  1820  		m.mtmDelta,
  1821  		m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin,
  1822  		m.mkt.LinearSlippageFactor,
  1823  		m.risk.GetRiskFactors().Short,
  1824  		m.risk.GetRiskFactors().Long,
  1825  		true,
  1826  		wasOpeningAuction); err != nil {
  1827  		if evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()); evt != nil {
  1828  			m.broker.Send(evt)
  1829  		}
  1830  
  1831  		// start the  monitoring auction if required
  1832  		if m.as.AuctionStart() {
  1833  			m.enterAuction(ctx)
  1834  		}
  1835  	}
  1836  	m.markPriceLock.Unlock()
  1837  	if wasOpeningAuction && !m.as.IsOpeningAuction() && m.getCurrentMarkPrice().IsZero() {
  1838  		m.markPriceCalculator.OverridePrice(m.lastTradedPrice)
  1839  		m.pMonitor.ResetPriceHistory(m.lastTradedPrice)
  1840  	}
  1841  	m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())
  1842  	if m.perp {
  1843  		if m.internalCompositePriceCalculator != nil {
  1844  			m.internalCompositePriceCalculator.CalculateBookMarkPriceAtTimeT(m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin, m.mkt.LinearSlippageFactor, m.risk.GetRiskFactors().Short, m.risk.GetRiskFactors().Long, t, m.matching)
  1845  			m.internalCompositePriceCalculator.CalculateMarkPrice(
  1846  				ctx,
  1847  				m.pMonitor,
  1848  				m.as,
  1849  				t,
  1850  				m.matching,
  1851  				m.internalCompositePriceFrequency,
  1852  				m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin,
  1853  				m.mkt.LinearSlippageFactor,
  1854  				m.risk.GetRiskFactors().Short,
  1855  				m.risk.GetRiskFactors().Long,
  1856  				false, false)
  1857  
  1858  			if wasOpeningAuction && (m.getCurrentInternalCompositePrice().IsZero()) {
  1859  				m.internalCompositePriceCalculator.OverridePrice(m.lastTradedPrice)
  1860  			}
  1861  			m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentInternalCompositePrice(), m.timeService.GetTimeNow().UnixNano())
  1862  		} else {
  1863  			m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, m.getCurrentMarkPrice(), m.timeService.GetTimeNow().UnixNano())
  1864  		}
  1865  	}
  1866  
  1867  	m.checkForReferenceMoves(ctx, updatedOrders, true)
  1868  
  1869  	m.checkBondBalance(ctx)
  1870  
  1871  	if !m.as.InAuction() {
  1872  		// only send the auction-left event if we actually *left* the auction.
  1873  		m.broker.Send(endEvt)
  1874  		// now that we've left the auction and all the orders have been unparked,
  1875  		// we can mark all positions using the margin calculation method appropriate
  1876  		// for non-auction mode and carry out any closeouts if need be
  1877  		if m.confirmMTM(ctx, false) {
  1878  			// set next MTM
  1879  			m.nextMTM = m.timeService.GetTimeNow().Add(m.mtmDelta)
  1880  		}
  1881  		// we have just left auction, check the network position, dispose of volume if possible
  1882  		m.checkNetwork(ctx, now)
  1883  	}
  1884  }
  1885  
  1886  func (m *Market) validateOrder(ctx context.Context, order *types.Order) (err error) {
  1887  	defer func() {
  1888  		if err != nil {
  1889  			order.Status = types.OrderStatusRejected
  1890  			m.broker.Send(events.NewOrderEvent(ctx, order))
  1891  		}
  1892  	}()
  1893  
  1894  	// Check we are allowed to handle this order type with the current market status
  1895  	isAuction := m.as.InAuction()
  1896  	if isAuction && order.TimeInForce == types.OrderTimeInForceGFN {
  1897  		order.Status = types.OrderStatusRejected
  1898  		order.Reason = types.OrderErrorCannotSendGFNOrderDuringAnAuction
  1899  		return common.ErrGFNOrderReceivedAuctionTrading
  1900  	}
  1901  
  1902  	if isAuction && order.TimeInForce == types.OrderTimeInForceIOC {
  1903  		order.Reason = types.OrderErrorCannotSendIOCOrderDuringAuction
  1904  		return common.ErrIOCOrderReceivedAuctionTrading
  1905  	}
  1906  
  1907  	if isAuction && order.TimeInForce == types.OrderTimeInForceFOK {
  1908  		order.Reason = types.OrderErrorCannotSendFOKOrderDurinAuction
  1909  		return common.ErrFOKOrderReceivedAuctionTrading
  1910  	}
  1911  
  1912  	if !isAuction && order.TimeInForce == types.OrderTimeInForceGFA {
  1913  		order.Reason = types.OrderErrorGFAOrderDuringContinuousTrading
  1914  		return common.ErrGFAOrderReceivedDuringContinuousTrading
  1915  	}
  1916  
  1917  	// Check the expiry time is valid
  1918  	if order.ExpiresAt > 0 && order.ExpiresAt < order.CreatedAt {
  1919  		order.Reason = types.OrderErrorInvalidExpirationDatetime
  1920  		return common.ErrInvalidExpiresAtTime
  1921  	}
  1922  
  1923  	if m.closed {
  1924  		// adding order to the buffer first
  1925  		order.Reason = types.OrderErrorMarketClosed
  1926  		return common.ErrMarketClosed
  1927  	}
  1928  
  1929  	if order.Type == types.OrderTypeNetwork {
  1930  		order.Reason = types.OrderErrorInvalidType
  1931  		return common.ErrInvalidOrderType
  1932  	}
  1933  
  1934  	// Validate market
  1935  	if order.MarketID != m.mkt.ID {
  1936  		// adding order to the buffer first
  1937  		order.Reason = types.OrderErrorInvalidMarketID
  1938  		if m.log.GetLevel() == logging.DebugLevel {
  1939  			m.log.Debug("Market ID mismatch",
  1940  				logging.Order(*order),
  1941  				logging.String("market", m.mkt.ID))
  1942  		}
  1943  		return types.ErrInvalidMarketID
  1944  	}
  1945  
  1946  	// Validate pegged orders
  1947  	if order.PeggedOrder != nil {
  1948  		if m.getMarginMode(order.Party) != types.MarginModeCrossMargin {
  1949  			return types.ErrPeggedOrdersNotAllowedInIsolatedMargin
  1950  		}
  1951  		if reason := order.ValidatePeggedOrder(); reason != types.OrderErrorUnspecified {
  1952  			order.Reason = reason
  1953  			if m.log.GetLevel() == logging.DebugLevel {
  1954  				m.log.Debug("Failed to validate pegged order details",
  1955  					logging.Order(*order),
  1956  					logging.String("market", m.mkt.ID))
  1957  			}
  1958  			return reason
  1959  		}
  1960  		if order.PeggedOrder.Reference == types.PeggedReferenceMid {
  1961  			offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor))
  1962  			tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor))
  1963  			if offsetInAsset.IsZero() && tickSizeInAsset.IsZero() {
  1964  				return fmt.Errorf("invalid offset - pegged mid will cross")
  1965  			}
  1966  		}
  1967  		return m.validateTickSize(order.PeggedOrder.Offset)
  1968  	}
  1969  
  1970  	if order.OriginalPrice != nil {
  1971  		return m.validateTickSize(order.OriginalPrice)
  1972  	}
  1973  
  1974  	return nil
  1975  }
  1976  
  1977  // validateOrder checks that the order parameters are valid for the market.
  1978  // NB: price in market, tickSize in market decimals.
  1979  func (m *Market) validateTickSize(price *num.Uint) error {
  1980  	d := num.UintZero().Mod(price, m.mkt.TickSize)
  1981  	if !d.IsZero() {
  1982  		return types.ErrOrderNotInTickSize
  1983  	}
  1984  	return nil
  1985  }
  1986  
  1987  func (m *Market) validateAccounts(ctx context.Context, order *types.Order) error {
  1988  	if !m.collateral.HasGeneralAccount(order.Party, m.settlementAsset) {
  1989  		// adding order to the buffer first
  1990  		order.Status = types.OrderStatusRejected
  1991  		order.Reason = types.OrderErrorInsufficientAssetBalance
  1992  		m.broker.Send(events.NewOrderEvent(ctx, order))
  1993  
  1994  		// party should be created before even trying to post order
  1995  		return common.ErrPartyInsufficientAssetBalance
  1996  	}
  1997  
  1998  	// ensure party have a general account, and margin account is / can be created
  1999  	_, err := m.collateral.CreatePartyMarginAccount(ctx, order.Party, order.MarketID, m.settlementAsset)
  2000  	if err != nil {
  2001  		m.log.Error("Margin account verification failed",
  2002  			logging.String("party-id", order.Party),
  2003  			logging.String("market-id", m.GetID()),
  2004  			logging.String("asset", m.settlementAsset),
  2005  		)
  2006  		// adding order to the buffer first
  2007  		order.Status = types.OrderStatusRejected
  2008  		order.Reason = types.OrderErrorMissingGeneralAccount
  2009  		m.broker.Send(events.NewOrderEvent(ctx, order))
  2010  		return common.ErrMissingGeneralAccountForParty
  2011  	}
  2012  
  2013  	// from this point we know the party have a margin account
  2014  	// we had it to the list of parties.
  2015  	if m.addParty(order.Party) {
  2016  		// First time seeing the party, we report his margin mode.
  2017  		m.emitPartyMarginModeUpdated(ctx, order.Party, m.getMarginMode(order.Party), m.getMarginFactor(order.Party))
  2018  	}
  2019  	return nil
  2020  }
  2021  
  2022  func (m *Market) releaseMarginExcess(ctx context.Context, partyID string) {
  2023  	// if this position went 0
  2024  	pos, ok := m.position.GetPositionByPartyID(partyID)
  2025  	if !ok {
  2026  		// the party has closed their position and it's been removed from the
  2027  		// position engine. Let's just create an empty one, so it can be cleared
  2028  		// down the line.
  2029  		pos = positions.NewMarketPosition(partyID)
  2030  	}
  2031  	m.releaseExcessMargin(ctx, pos)
  2032  }
  2033  
  2034  // releaseExcessMargin does what releaseMarginExcess does. Added this function to be able to release
  2035  // all excess margin on MTM without having to call the latter by iterating all positions, and then
  2036  // fetching said position again my party.
  2037  func (m *Market) releaseExcessMargin(ctx context.Context, positions ...events.MarketPosition) {
  2038  	evts := make([]events.Event, 0, len(positions))
  2039  	mEvts := make([]events.Event, 0, len(positions))
  2040  	mktID := m.GetID()
  2041  	// base margin event. We don't care about the uint values being pointers here
  2042  	// this is only used to create an event, which converts this to proto.
  2043  	marginEvt := types.MarginLevels{
  2044  		MaintenanceMargin:      num.UintZero(),
  2045  		SearchLevel:            num.UintZero(),
  2046  		InitialMargin:          num.UintZero(),
  2047  		CollateralReleaseLevel: num.UintZero(),
  2048  		OrderMargin:            num.UintZero(),
  2049  		MarketID:               mktID,
  2050  		Asset:                  m.settlementAsset,
  2051  		Timestamp:              m.timeService.GetTimeNow().UnixNano(),
  2052  	}
  2053  	for _, pos := range positions {
  2054  		party := pos.Party()
  2055  		// if the party still have a position in the settlement engine,
  2056  		// do not remove them for now
  2057  		if m.settlement.HasPosition(party) {
  2058  			continue
  2059  		}
  2060  
  2061  		// now check if all buy/sell/size are 0
  2062  		if pos.Buy() != 0 || pos.Sell() != 0 || pos.Size() != 0 {
  2063  			// position is not 0, nothing to release surely
  2064  			continue
  2065  		}
  2066  
  2067  		// If no error is returned, the party either had a zero balance, or no margin balance left.
  2068  		// Either way their margin levels are zero, so we need to emit an event saying as much.
  2069  		transfers, err := m.collateral.ClearPartyMarginAccount(
  2070  			ctx, party, mktID, m.settlementAsset)
  2071  		if err != nil {
  2072  			m.log.Error("unable to clear party margin account", logging.Error(err))
  2073  			continue
  2074  		}
  2075  		marginEvt.Party = party
  2076  		marginEvt.MarginFactor = m.getMarginFactor(party)
  2077  		marginEvt.MarginMode = m.getMarginMode(party)
  2078  		mEvts = append(mEvts, events.NewMarginLevelsEvent(ctx, marginEvt))
  2079  
  2080  		if transfers != nil {
  2081  			evts = append(evts, events.NewLedgerMovements(
  2082  				ctx, []*types.LedgerMovement{transfers}),
  2083  			)
  2084  		}
  2085  		if marginEvt.MarginMode == types.MarginModeIsolatedMargin {
  2086  			transfers, err = m.collateral.ClearPartyOrderMarginAccount(
  2087  				ctx, party, mktID, m.settlementAsset)
  2088  			if err != nil {
  2089  				m.log.Error("unable to clear party order margin account", logging.Error(err))
  2090  				continue
  2091  			}
  2092  			if transfers != nil {
  2093  				evts = append(evts, events.NewLedgerMovements(
  2094  					ctx, []*types.LedgerMovement{transfers}),
  2095  				)
  2096  			}
  2097  		}
  2098  		// we can delete the party from the map here
  2099  		// unless the party is an LP
  2100  		if !m.liquidityEngine.IsLiquidityProvider(party) {
  2101  			delete(m.parties, party)
  2102  		}
  2103  	}
  2104  	if len(evts) > 0 {
  2105  		m.broker.SendBatch(evts)
  2106  	}
  2107  	if len(mEvts) > 0 {
  2108  		m.broker.SendBatch(mEvts)
  2109  	}
  2110  }
  2111  
  2112  func rejectStopOrders(rejectionReason types.StopOrderRejectionReason, orders ...*types.StopOrder) {
  2113  	for _, o := range orders {
  2114  		if o != nil {
  2115  			o.Status = types.StopOrderStatusRejected
  2116  			o.RejectionReason = ptr.From(rejectionReason)
  2117  		}
  2118  	}
  2119  }
  2120  
  2121  func (m *Market) SubmitStopOrdersWithIDGeneratorAndOrderIDs(
  2122  	ctx context.Context,
  2123  	submission *types.StopOrdersSubmission,
  2124  	party string,
  2125  	idgen common.IDGenerator,
  2126  	fallsBelowID, risesAboveID *string,
  2127  ) (*types.OrderConfirmation, error) {
  2128  	m.idgen = idgen
  2129  	defer func() { m.idgen = nil }()
  2130  
  2131  	fallsBelow, risesAbove := submission.IntoStopOrders(
  2132  		party, ptr.UnBox(fallsBelowID), ptr.UnBox(risesAboveID), m.timeService.GetTimeNow())
  2133  
  2134  	defer func() {
  2135  		evts := []events.Event{}
  2136  		if fallsBelow != nil {
  2137  			evts = append(evts, events.NewStopOrderEvent(ctx, fallsBelow))
  2138  		}
  2139  		if risesAbove != nil {
  2140  			evts = append(evts, events.NewStopOrderEvent(ctx, risesAbove))
  2141  		}
  2142  
  2143  		if len(evts) > 0 {
  2144  			m.broker.SendBatch(evts)
  2145  		}
  2146  	}()
  2147  
  2148  	if m.IsOpeningAuction() {
  2149  		rejectStopOrders(types.StopOrderRejectionNotAllowedDuringOpeningAuction, fallsBelow, risesAbove)
  2150  		return nil, common.ErrStopOrderNotAllowedDuringOpeningAuction
  2151  	}
  2152  
  2153  	if !m.canTrade() {
  2154  		rejectStopOrders(types.StopOrderRejectionTradingNotAllowed, fallsBelow, risesAbove)
  2155  		return nil, common.ErrTradingNotAllowed
  2156  	}
  2157  
  2158  	now := m.timeService.GetTimeNow()
  2159  	orderCnt := 0
  2160  	if fallsBelow != nil {
  2161  		if fallsBelow.Expiry.Expires() && fallsBelow.Expiry.ExpiresAt.Before(now) {
  2162  			rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove)
  2163  			return nil, common.ErrStopOrderExpiryInThePast
  2164  		}
  2165  		if !fallsBelow.OrderSubmission.ReduceOnly {
  2166  			rejectStopOrders(types.StopOrderRejectionMustBeReduceOnly, fallsBelow, risesAbove)
  2167  			return nil, common.ErrStopOrderMustBeReduceOnly
  2168  		}
  2169  		orderCnt++
  2170  	}
  2171  	if risesAbove != nil {
  2172  		if risesAbove.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Before(now) {
  2173  			rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove)
  2174  			return nil, common.ErrStopOrderExpiryInThePast
  2175  		}
  2176  		if !risesAbove.OrderSubmission.ReduceOnly {
  2177  			rejectStopOrders(types.StopOrderRejectionMustBeReduceOnly, fallsBelow, risesAbove)
  2178  			return nil, common.ErrStopOrderMustBeReduceOnly
  2179  		}
  2180  		orderCnt++
  2181  	}
  2182  
  2183  	if risesAbove != nil && fallsBelow != nil {
  2184  		if risesAbove.Expiry.Expires() && fallsBelow.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Compare(*fallsBelow.Expiry.ExpiresAt) == 0 {
  2185  			rejectStopOrders(types.StopOrderRejectionOCONotAllowedSameExpiryTime, fallsBelow, risesAbove)
  2186  			return nil, common.ErrStopOrderNotAllowedSameExpiry
  2187  		}
  2188  	}
  2189  
  2190  	// now check if that party hasn't exceeded the max amount per market
  2191  	if m.stopOrders.CountForParty(party)+uint64(orderCnt) > m.maxStopOrdersPerParties.Uint64() {
  2192  		rejectStopOrders(types.StopOrderRejectionMaxStopOrdersPerPartyReached, fallsBelow, risesAbove)
  2193  		return nil, common.ErrMaxStopOrdersPerPartyReached
  2194  	}
  2195  
  2196  	// now check for the parties position
  2197  	if positions := m.position.GetPositionsByParty(party); len(positions) > 1 {
  2198  		m.log.Panic("only one position expected", logging.Int("got", len(positions)))
  2199  	} else if len(positions) < 1 {
  2200  		rejectStopOrders(types.StopOrderRejectionNotAllowedWithoutAPosition, fallsBelow, risesAbove)
  2201  		return nil, common.ErrStopOrderSubmissionNotAllowedWithoutExistingPosition
  2202  	}
  2203  
  2204  	fallsBelowTriggered, risesAboveTriggered := m.stopOrderWouldTriggerAtSubmission(fallsBelow),
  2205  		m.stopOrderWouldTriggerAtSubmission(risesAbove)
  2206  	triggered := fallsBelowTriggered || risesAboveTriggered
  2207  
  2208  	// if the stop order links to a position, see if we are scaling the size
  2209  	if fallsBelow != nil && fallsBelow.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition {
  2210  		if fallsBelow.SizeOverrideValue != nil {
  2211  			if fallsBelow.SizeOverrideValue.PercentageSize.LessThanOrEqual(num.DecimalFromFloat(0.0)) ||
  2212  				fallsBelow.SizeOverrideValue.PercentageSize.GreaterThan(num.DecimalFromFloat(1.0)) {
  2213  				rejectStopOrders(types.StopOrderRejectionLinkedPercentageInvalid, fallsBelow, risesAbove)
  2214  				return nil, common.ErrStopOrderSizeOverridePercentageInvalid
  2215  			}
  2216  		}
  2217  	}
  2218  
  2219  	if risesAbove != nil && risesAbove.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition {
  2220  		if risesAbove.SizeOverrideValue != nil {
  2221  			if risesAbove.SizeOverrideValue.PercentageSize.LessThanOrEqual(num.DecimalFromFloat(0.0)) ||
  2222  				risesAbove.SizeOverrideValue.PercentageSize.GreaterThan(num.DecimalFromFloat(1.0)) {
  2223  				rejectStopOrders(types.StopOrderRejectionLinkedPercentageInvalid, fallsBelow, risesAbove)
  2224  				return nil, common.ErrStopOrderSizeOverridePercentageInvalid
  2225  			}
  2226  		}
  2227  	}
  2228  
  2229  	// if we are in an auction
  2230  	// or no order is triggered
  2231  	// let's just submit it straight away
  2232  	if m.as.InAuction() || !triggered {
  2233  		m.poolStopOrders(fallsBelow, risesAbove)
  2234  		return nil, nil
  2235  	}
  2236  
  2237  	var confirmation *types.OrderConfirmation
  2238  	var err error
  2239  	// now would the order get trigger straight away?
  2240  	switch {
  2241  	case fallsBelowTriggered:
  2242  		fallsBelow.Status = types.StopOrderStatusTriggered
  2243  		if risesAbove != nil {
  2244  			risesAbove.Status = types.StopOrderStatusStopped
  2245  		}
  2246  		fallsBelow.OrderID = idgen.NextID()
  2247  		confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID(
  2248  			ctx, fallsBelow.OrderSubmission, party, idgen, fallsBelow.OrderID, true,
  2249  		)
  2250  		if err != nil && confirmation != nil {
  2251  			fallsBelow.OrderID = confirmation.Order.ID
  2252  		}
  2253  	case risesAboveTriggered:
  2254  		risesAbove.Status = types.StopOrderStatusTriggered
  2255  		if fallsBelow != nil {
  2256  			fallsBelow.Status = types.StopOrderStatusStopped
  2257  		}
  2258  		risesAbove.OrderID = idgen.NextID()
  2259  		confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID(
  2260  			ctx, risesAbove.OrderSubmission, party, idgen, risesAbove.OrderID, true,
  2261  		)
  2262  		if err != nil && confirmation != nil {
  2263  			risesAbove.OrderID = confirmation.Order.ID
  2264  		}
  2265  	}
  2266  
  2267  	return confirmation, err
  2268  }
  2269  
  2270  func (m *Market) poolStopOrders(
  2271  	fallsBelow, risesAbove *types.StopOrder,
  2272  ) {
  2273  	if fallsBelow != nil {
  2274  		m.stopOrders.Insert(fallsBelow)
  2275  		if fallsBelow.Expiry.Expires() {
  2276  			m.expiringStopOrders.Insert(fallsBelow.ID, fallsBelow.Expiry.ExpiresAt.UnixNano())
  2277  		}
  2278  	}
  2279  	if risesAbove != nil {
  2280  		m.stopOrders.Insert(risesAbove)
  2281  		if risesAbove.Expiry.Expires() {
  2282  			m.expiringStopOrders.Insert(risesAbove.ID, risesAbove.Expiry.ExpiresAt.UnixNano())
  2283  		}
  2284  	}
  2285  }
  2286  
  2287  func (m *Market) stopOrderWouldTriggerAtSubmission(
  2288  	stopOrder *types.StopOrder,
  2289  ) bool {
  2290  	if m.lastTradedPrice == nil || stopOrder == nil || stopOrder.Trigger.IsTrailingPercentOffset() {
  2291  		return false
  2292  	}
  2293  
  2294  	lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice())
  2295  
  2296  	switch stopOrder.Trigger.Direction {
  2297  	case types.StopOrderTriggerDirectionFallsBelow:
  2298  		if lastTradedPrice.LTE(stopOrder.Trigger.Price()) {
  2299  			return true
  2300  		}
  2301  	case types.StopOrderTriggerDirectionRisesAbove:
  2302  		if lastTradedPrice.GTE(stopOrder.Trigger.Price()) {
  2303  			return true
  2304  		}
  2305  	}
  2306  	return false
  2307  }
  2308  
  2309  func (m *Market) triggerStopOrders(
  2310  	ctx context.Context,
  2311  	idgen common.IDGenerator,
  2312  ) []*types.OrderConfirmation {
  2313  	if m.lastTradedPrice == nil {
  2314  		return nil
  2315  	}
  2316  	lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice())
  2317  	triggered, cancelled := m.stopOrders.PriceUpdated(lastTradedPrice)
  2318  
  2319  	// See if there are any linked orders that are the wrong direction
  2320  	cancelled = append(cancelled, m.stopOrders.CheckDirection(m.position)...)
  2321  
  2322  	if len(triggered) <= 0 && len(cancelled) <= 0 {
  2323  		return nil
  2324  	}
  2325  
  2326  	now := m.timeService.GetTimeNow()
  2327  	// remove from expiring orders + set updatedAt
  2328  	for _, v := range append(triggered, cancelled...) {
  2329  		v.UpdatedAt = now
  2330  		if v.Expiry.Expires() {
  2331  			m.expiringStopOrders.RemoveOrder(v.Expiry.ExpiresAt.UnixNano(), v.ID)
  2332  		}
  2333  	}
  2334  
  2335  	evts := make([]events.Event, 0, len(cancelled))
  2336  	for _, v := range cancelled {
  2337  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  2338  	}
  2339  
  2340  	m.broker.SendBatch(evts)
  2341  
  2342  	if len(triggered) <= 0 {
  2343  		return nil
  2344  	}
  2345  
  2346  	confirmations := m.submitStopOrders(ctx, triggered, types.StopOrderStatusTriggered, idgen)
  2347  
  2348  	return append(m.triggerStopOrders(ctx, idgen), confirmations...)
  2349  }
  2350  
  2351  // SubmitOrder submits the given order.
  2352  func (m *Market) SubmitOrder(
  2353  	ctx context.Context,
  2354  	orderSubmission *types.OrderSubmission,
  2355  	party string,
  2356  	deterministicID string,
  2357  ) (oc *types.OrderConfirmation, _ error) {
  2358  	idgen := idgeneration.New(deterministicID)
  2359  	return m.SubmitOrderWithIDGeneratorAndOrderID(
  2360  		ctx, orderSubmission, party, idgen, idgen.NextID(), true,
  2361  	)
  2362  }
  2363  
  2364  // SubmitOrder submits the given order.
  2365  func (m *Market) SubmitOrderWithIDGeneratorAndOrderID(
  2366  	ctx context.Context,
  2367  	orderSubmission *types.OrderSubmission,
  2368  	party string,
  2369  	idgen common.IDGenerator,
  2370  	orderID string,
  2371  	checkForTriggers bool,
  2372  ) (oc *types.OrderConfirmation, _ error) {
  2373  	defer m.onTxProcessed()
  2374  
  2375  	m.idgen = idgen
  2376  	defer func() { m.idgen = nil }()
  2377  
  2378  	defer func() {
  2379  		if !checkForTriggers {
  2380  			return
  2381  		}
  2382  
  2383  		m.triggerStopOrders(ctx, idgen)
  2384  	}()
  2385  	order := orderSubmission.IntoOrder(party)
  2386  	order.CreatedAt = m.timeService.GetTimeNow().UnixNano()
  2387  	order.ID = orderID
  2388  	if order.Price != nil {
  2389  		order.OriginalPrice = order.Price.Clone()
  2390  		order.Price, _ = num.UintFromDecimal(order.Price.ToDecimal().Mul(m.priceFactor))
  2391  		if order.Type == types.OrderTypeLimit && order.PeggedOrder == nil && order.Price.IsZero() {
  2392  			// limit orders need to be priced > 0
  2393  			order.Status = types.OrderStatusRejected
  2394  			order.Reason = types.OrderErrorPriceNotInTickSize // @TODO add new error
  2395  			m.broker.Send(events.NewOrderEvent(ctx, order))
  2396  			return nil, common.ErrInvalidOrderPrice
  2397  		}
  2398  	}
  2399  	// check max price in case of capped market
  2400  	if m.capMax != nil && order.Price != nil && order.Price.GTE(m.capMax) {
  2401  		order.Status = types.OrderStatusRejected
  2402  		order.Reason = types.OrderErrorPriceLTEMaxPrice
  2403  		m.broker.Send(events.NewOrderEvent(ctx, order))
  2404  		return nil, common.ErrInvalidOrderPrice
  2405  	}
  2406  
  2407  	if !m.canTrade() {
  2408  		order.Status = types.OrderStatusRejected
  2409  		order.Reason = types.OrderErrorMarketClosed
  2410  		m.broker.Send(events.NewOrderEvent(ctx, order))
  2411  		return nil, common.ErrTradingNotAllowed
  2412  	}
  2413  
  2414  	conf, orderUpdates, err := m.submitOrder(ctx, order)
  2415  	if err != nil {
  2416  		return nil, err
  2417  	}
  2418  
  2419  	allUpdatedOrders := append(
  2420  		[]*types.Order{conf.Order}, conf.PassiveOrdersAffected...)
  2421  	allUpdatedOrders = append(allUpdatedOrders, orderUpdates...)
  2422  
  2423  	if !m.as.InAuction() {
  2424  		m.checkForReferenceMoves(ctx, allUpdatedOrders, false)
  2425  	}
  2426  
  2427  	return conf, nil
  2428  }
  2429  
  2430  func (m *Market) submitOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) {
  2431  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "SubmitOrder")
  2432  	orderValidity := "invalid"
  2433  	defer func() {
  2434  		timer.EngineTimeCounterAdd()
  2435  		metrics.OrderCounterInc(m.mkt.ID, orderValidity)
  2436  	}()
  2437  
  2438  	// set those at the beginning as even rejected order get through the buffers
  2439  	order.Version = common.InitialOrderVersion
  2440  	order.Status = types.OrderStatusActive
  2441  
  2442  	if err := m.validateOrder(ctx, order); err != nil {
  2443  		return nil, nil, err
  2444  	}
  2445  
  2446  	if err := m.validateAccounts(ctx, order); err != nil {
  2447  		return nil, nil, err
  2448  	}
  2449  
  2450  	if err := m.position.ValidateOrder(order); err != nil {
  2451  		return nil, nil, err
  2452  	}
  2453  
  2454  	// Now that validation is handled, call the code to place the order
  2455  	orderConf, orderUpdates, err := m.submitValidatedOrder(ctx, order)
  2456  	if err == nil {
  2457  		orderValidity = "valid"
  2458  	}
  2459  
  2460  	if order.PeggedOrder != nil && order.IsFinished() {
  2461  		// remove the pegged order from anywhere
  2462  		m.removePeggedOrder(order)
  2463  	}
  2464  
  2465  	// insert an expiring order if it's either in the book
  2466  	// or in the parked list
  2467  	if order.IsExpireable() && !order.IsFinished() {
  2468  		m.expiringOrders.Insert(order.ID, order.ExpiresAt)
  2469  	}
  2470  
  2471  	return orderConf, orderUpdates, err
  2472  }
  2473  
  2474  func (m *Market) submitValidatedOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) {
  2475  	isPegged := order.PeggedOrder != nil
  2476  	if isPegged {
  2477  		order.Status = types.OrderStatusParked
  2478  		order.Reason = types.OrderErrorUnspecified
  2479  
  2480  		if m.as.InAuction() {
  2481  			order.SetIcebergPeaks()
  2482  
  2483  			m.peggedOrders.Park(order)
  2484  			// If we are in an auction, we don't insert this order into the book
  2485  			// Maybe should return an orderConfirmation with order state PARKED
  2486  			m.broker.Send(events.NewOrderEvent(ctx, order))
  2487  			return &types.OrderConfirmation{Order: order}, nil, nil
  2488  		}
  2489  		// Reprice
  2490  		err := m.repricePeggedOrder(order)
  2491  		if err != nil {
  2492  			order.SetIcebergPeaks()
  2493  			m.peggedOrders.Park(order)
  2494  			m.broker.Send(events.NewOrderEvent(ctx, order))
  2495  			return &types.OrderConfirmation{Order: order}, nil, nil // nolint
  2496  		}
  2497  	}
  2498  
  2499  	// Register order as potential positions
  2500  	pos := m.position.RegisterOrder(ctx, order)
  2501  
  2502  	// in case we have an IOC order, that would work but need to be stopped because
  2503  	// it'd be flipping the position of the party
  2504  	// first check if we have a reduce only order and make sure it can go through
  2505  	if order.ReduceOnly {
  2506  		reduce, extraSize := pos.OrderReducesOnlyExposure(order)
  2507  		// if we are not reducing, or if the position flips on a FOK, we short-circuit here.
  2508  		// in the case of a IOC, the order will be stopped once we reach 0
  2509  		if !reduce || (order.TimeInForce == types.OrderTimeInForceFOK && extraSize > 0) {
  2510  			return nil, nil, m.unregisterAndReject(
  2511  				ctx, order, types.ErrReduceOnlyOrderWouldNotReducePosition)
  2512  		}
  2513  		// keep track of the eventual reduce only size
  2514  		order.ReduceOnlyAdjustRemaining(extraSize)
  2515  	}
  2516  	marginMode := m.getMarginMode(order.Party)
  2517  
  2518  	// Perform check and allocate margin unless the order is (partially) closing the party position
  2519  	// NB: this is only done at this point for cross margin mode
  2520  	if marginMode == types.MarginModeCrossMargin && !order.ReduceOnly && !pos.OrderReducesExposure(order) {
  2521  		if err := m.checkMarginForOrder(ctx, pos, order); err != nil {
  2522  			if m.log.GetLevel() <= logging.DebugLevel {
  2523  				m.log.Debug("Unable to check/add margin for party",
  2524  					logging.Order(*order), logging.Error(err))
  2525  			}
  2526  			_ = m.unregisterAndReject(
  2527  				ctx, order, types.OrderErrorMarginCheckFailed)
  2528  			return nil, nil, common.ErrMarginCheckFailed
  2529  		}
  2530  	}
  2531  
  2532  	// from here we may have assigned some margin.
  2533  	// we add the check to roll it back in case we have a 0 positions after this
  2534  	defer m.releaseMarginExcess(ctx, order.Party)
  2535  
  2536  	// If we are not in an opening auction, apply fees
  2537  	var trades []*types.Trade
  2538  	var fees events.FeesTransfer
  2539  	// we're not in auction (not opening, not any other auction
  2540  	if !m.as.InAuction() {
  2541  		// first we call the order book to evaluate auction triggers and get the list of trades
  2542  		var err error
  2543  		trades, err = m.checkPriceAndGetTrades(ctx, order)
  2544  		if err != nil {
  2545  			return nil, nil, m.unregisterAndReject(ctx, order, err)
  2546  		}
  2547  
  2548  		// try to apply fees on the trade
  2549  		fees, err = m.calcFees(trades)
  2550  		if err != nil {
  2551  			return nil, nil, m.unregisterAndReject(ctx, order, err)
  2552  		}
  2553  	}
  2554  
  2555  	// if an auction was trigger, and we are a pegged order
  2556  	// or a liquidity order, let's return now.
  2557  	if m.as.InAuction() && isPegged {
  2558  		if isPegged {
  2559  			m.peggedOrders.Park(order)
  2560  		}
  2561  		// parking the order, needs to unregister it first
  2562  		_ = m.position.UnregisterOrder(ctx, order)
  2563  		m.broker.Send(events.NewOrderEvent(ctx, order))
  2564  		return &types.OrderConfirmation{Order: order}, nil, nil
  2565  	}
  2566  
  2567  	order.Status = types.OrderStatusActive
  2568  
  2569  	var aggressorFee *num.Uint
  2570  	if fees != nil {
  2571  		aggressorFee = fees.TotalFeesAmountPerParty()[order.Party]
  2572  	}
  2573  
  2574  	// NB: this is the position with the trades included and the order sizes updated to remaining!!!
  2575  	// NB: this is not touching the actual position from the position engine but is all done on a clone, so that
  2576  	// in handle confirmation this will be done as per normal.
  2577  	posWithTrades := pos.UpdateInPlaceOnTrades(m.log, order.Side, trades, order)
  2578  	// First, check whether the order will trade, either fully or in part, immediately upon entry. If so:
  2579  	// If the trade would increase the party's position, the required additional funds as specified in the Increasing Position section will be calculated.
  2580  	// The total expected margin balance (current plus new funds) will then be compared to the maintenance margin for the expected position,
  2581  	// if the margin balance would be less than maintenance, instead reject the order in it's entirety.
  2582  	// If the margin will be greater than the maintenance margin their general account will be checked for sufficient funds.
  2583  	// If they have sufficient, that amount will be moved into their margin account and the immediately matching portion of the order will trade.
  2584  	// If they do not have sufficient, the order will be rejected in it's entirety for not meeting margin requirements.
  2585  	// If the trade would decrease the party's position, that portion will trade and margin will be released as in the Decreasing Position.
  2586  	// If the order is not persistent this is the end, if it is persistent any portion of the order which
  2587  	// has not traded in step 1 will move to being placed on the order book.
  2588  	if len(trades) > 0 {
  2589  		if marginMode == types.MarginModeIsolatedMargin {
  2590  			// check that the party can cover the trade AND the fees
  2591  			if err := m.updateIsolatedMarginOnAggressor(ctx, posWithTrades, order, trades, false, aggressorFee); err != nil {
  2592  				if m.log.GetLevel() <= logging.DebugLevel {
  2593  					m.log.Debug("Unable to check/add immediate trade margin for party",
  2594  						logging.Order(*order), logging.Error(err))
  2595  				}
  2596  				_ = m.position.UnregisterOrder(ctx, order)
  2597  				return nil, nil, common.ErrMarginCheckFailed
  2598  			}
  2599  		} else if aggressorFee != nil {
  2600  			if err := m.collateral.PartyCanCoverFees(m.settlementAsset, m.mkt.ID, order.Party, aggressorFee); err != nil {
  2601  				m.log.Error("insufficient funds to cover fees", logging.Order(order), logging.Error(err))
  2602  				m.unregisterAndReject(ctx, order, types.OrderErrorInsufficientFundsToPayFees)
  2603  				return nil, nil, err
  2604  			}
  2605  		}
  2606  		if order.Type == types.OrderTypeMarket && marginMode == types.MarginModeCrossMargin && !order.ReduceOnly && !pos.OrderReducesExposure(order) {
  2607  			if err := m.checkMarginForOrder(ctx, posWithTrades, order); err != nil {
  2608  				if m.log.GetLevel() <= logging.DebugLevel {
  2609  					m.log.Debug("Unable to check/add margin for party",
  2610  						logging.Order(*order), logging.Error(err))
  2611  				}
  2612  				_ = m.unregisterAndReject(
  2613  					ctx, order, types.OrderErrorMarginCheckFailed)
  2614  				return nil, nil, common.ErrMarginCheckFailed
  2615  			}
  2616  		}
  2617  	}
  2618  
  2619  	// Send the aggressive order into matching engine
  2620  	confirmation, err := m.matching.SubmitOrder(order)
  2621  	if err != nil {
  2622  		return nil, nil, m.unregisterAndReject(ctx, order, err)
  2623  	}
  2624  
  2625  	// this is no op for non reduce-only orders
  2626  	order.ClearUpExtraRemaining()
  2627  
  2628  	// this means our reduce-only order (IOC) have been stopped
  2629  	// from trading to the point it would flip the position,
  2630  	// and successfully reduced the position to 0.
  2631  	// set the status to Stopped then.
  2632  	if order.ReduceOnly && order.Remaining > 0 {
  2633  		order.Status = types.OrderStatusStopped
  2634  	}
  2635  
  2636  	// if the order is not staying in the book, then we remove it
  2637  	// from the potential positions
  2638  	if order.IsFinished() && order.Remaining > 0 {
  2639  		_ = m.position.UnregisterOrder(ctx, order)
  2640  	}
  2641  
  2642  	// we replace the trades in the confirmation with the one we got initially
  2643  	// the contains the fees information
  2644  	confirmation.Trades = trades
  2645  
  2646  	if marginMode == types.MarginModeIsolatedMargin && order.Status == types.OrderStatusActive && order.TrueRemaining() > 0 {
  2647  		// now we need to check if the party has sufficient funds to cover the order margin for the remaining size
  2648  		// if not the remaining order is cancelled.
  2649  		// if successful the required order margin are transferred to the order margin account.
  2650  		if err := m.updateIsolatedMarginOnOrder(ctx, posWithTrades, order); err != nil {
  2651  			if m.log.GetLevel() <= logging.DebugLevel {
  2652  				m.log.Debug("Unable to check/add margin for party",
  2653  					logging.Order(*order), logging.Error(err))
  2654  			}
  2655  			_ = m.unregisterAndReject(
  2656  				ctx, order, types.OrderErrorMarginCheckFailed)
  2657  			m.matching.RemoveOrder(order.ID)
  2658  			if len(trades) > 0 {
  2659  				if err = m.applyFees(ctx, order, fees); err != nil {
  2660  					m.log.Panic("failed to apply fees on order", logging.Order(order), logging.String("aggressor total fees", fees.TotalFeesAmountPerParty()[order.ID].String()), logging.Error(err))
  2661  				}
  2662  				// if there were trades we need to return the confirmation so the trades can be handled
  2663  				// otherwise they were just removed from the book for the passive side and gone
  2664  				orderUpdates := m.handleConfirmation(ctx, confirmation, nil)
  2665  				return confirmation, orderUpdates, common.ErrMarginCheckFailed
  2666  			}
  2667  			return nil, nil, common.ErrMarginCheckFailed
  2668  		}
  2669  	}
  2670  
  2671  	if fees != nil {
  2672  		if err = m.applyFees(ctx, order, fees); err != nil {
  2673  			m.log.Panic("failed to apply fees on order", logging.Order(order), logging.String("aggressor total fees", fees.TotalFeesAmountPerParty()[order.ID].String()), logging.Error(err))
  2674  		}
  2675  	}
  2676  
  2677  	// Send out the order update here as handling the confirmation message
  2678  	// below might trigger an action that can change the order details.
  2679  	m.broker.Send(events.NewOrderEvent(ctx, order))
  2680  
  2681  	orderUpdates := m.handleConfirmation(ctx, confirmation, nil)
  2682  	return confirmation, orderUpdates, nil
  2683  }
  2684  
  2685  func (m *Market) checkPriceAndGetTrades(ctx context.Context, order *types.Order) ([]*types.Trade, error) {
  2686  	trades, err := m.matching.GetTrades(order)
  2687  	if err != nil {
  2688  		return nil, err
  2689  	}
  2690  
  2691  	if order.PostOnly && len(trades) > 0 {
  2692  		return nil, types.OrderErrorPostOnlyOrderWouldTrade
  2693  	}
  2694  
  2695  	persistent := true
  2696  	switch order.TimeInForce {
  2697  	case types.OrderTimeInForceFOK, types.OrderTimeInForceGFN, types.OrderTimeInForceIOC:
  2698  		persistent = false
  2699  	}
  2700  
  2701  	for _, t := range trades {
  2702  		if m.pMonitor.CheckPrice(ctx, m.as, t.Price, persistent, false) {
  2703  			return nil, types.OrderErrorNonPersistentOrderOutOfPriceBounds
  2704  		}
  2705  		if evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()); evt != nil {
  2706  			m.broker.Send(evt)
  2707  		}
  2708  		// start the  monitoring auction if required?
  2709  		if m.as.AuctionStart() {
  2710  			m.enterAuction(ctx)
  2711  			return nil, nil
  2712  		}
  2713  	}
  2714  
  2715  	return trades, nil
  2716  }
  2717  
  2718  // addParty returns true if the party is new to the market, false otherwise.
  2719  func (m *Market) addParty(party string) bool {
  2720  	_, registered := m.parties[party]
  2721  	if !registered {
  2722  		m.parties[party] = struct{}{}
  2723  	}
  2724  	return !registered
  2725  }
  2726  
  2727  func (m *Market) calcFees(trades []*types.Trade) (events.FeesTransfer, error) {
  2728  	// if we have some trades, let's try to get the fees
  2729  
  2730  	if len(trades) <= 0 || m.as.IsOpeningAuction() {
  2731  		return nil, nil
  2732  	}
  2733  
  2734  	// first we get the fees for these trades
  2735  	var (
  2736  		fees events.FeesTransfer
  2737  		err  error
  2738  	)
  2739  
  2740  	if !m.as.InAuction() {
  2741  		fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService)
  2742  	} else if m.as.IsMonitorAuction() {
  2743  		// we are in auction mode
  2744  		fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService)
  2745  	} else if m.as.IsFBA() {
  2746  		fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService)
  2747  	}
  2748  
  2749  	if err != nil {
  2750  		return nil, err
  2751  	}
  2752  	return fees, nil
  2753  }
  2754  
  2755  func (m *Market) applyFees(ctx context.Context, order *types.Order, fees events.FeesTransfer) error {
  2756  	var transfers []*types.LedgerMovement
  2757  	var err error
  2758  
  2759  	if !m.as.InAuction() {
  2760  		transfers, err = m.collateral.TransferFeesContinuousTrading(ctx, m.GetID(), m.settlementAsset, fees)
  2761  	} else if m.as.IsMonitorAuction() {
  2762  		// @TODO handle this properly
  2763  		transfers, err = m.collateral.TransferFees(ctx, m.GetID(), m.settlementAsset, fees)
  2764  	} else if m.as.IsFBA() {
  2765  		// @TODO implement transfer for auction types
  2766  		transfers, err = m.collateral.TransferFees(ctx, m.GetID(), m.settlementAsset, fees)
  2767  	}
  2768  
  2769  	if err != nil {
  2770  		m.log.Error("unable to transfer fees for trades",
  2771  			logging.String("order-id", order.ID),
  2772  			logging.String("market-id", m.GetID()),
  2773  			logging.Error(err))
  2774  		return types.OrderErrorInsufficientFundsToPayFees
  2775  	}
  2776  
  2777  	// send transfers through the broker
  2778  	if len(transfers) > 0 {
  2779  		m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  2780  	}
  2781  
  2782  	m.marketActivityTracker.UpdateFeesFromTransfers(m.settlementAsset, m.GetID(), fees.Transfers())
  2783  
  2784  	return nil
  2785  }
  2786  
  2787  func (m *Market) handleConfirmationPassiveOrders(
  2788  	ctx context.Context,
  2789  	conf *types.OrderConfirmation,
  2790  ) {
  2791  	if conf.PassiveOrdersAffected != nil {
  2792  		evts := make([]events.Event, 0, len(conf.PassiveOrdersAffected))
  2793  
  2794  		// Insert or update passive orders siting on the book
  2795  		for _, order := range conf.PassiveOrdersAffected {
  2796  			// if the order was generated by an offbook source such as an AMM we need to register the order here
  2797  			// since it was never registered as an incoming order.
  2798  			if order.GeneratedOffbook {
  2799  				cpy := order.Clone()
  2800  				cpy.Remaining = cpy.Size // remaining will be 0 since it has traded, so we copy it back to its full size to register
  2801  				m.position.RegisterOrder(ctx, cpy)
  2802  			}
  2803  
  2804  			// set the `updatedAt` value as these orders have changed
  2805  			order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
  2806  			evts = append(evts, events.NewOrderEvent(ctx, order))
  2807  
  2808  			// If the order is a pegged order and is complete we must remove it from the pegged list
  2809  			if order.PeggedOrder != nil {
  2810  				if order.Remaining == 0 || order.Status != types.OrderStatusActive {
  2811  					m.removePeggedOrder(order)
  2812  				}
  2813  			}
  2814  
  2815  			// remove the order from the expiring list
  2816  			// if it was a GTT order
  2817  			if order.IsExpireable() && order.IsFinished() {
  2818  				m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID)
  2819  			}
  2820  		}
  2821  
  2822  		m.broker.SendBatch(evts)
  2823  	}
  2824  }
  2825  
  2826  func decreasedPosition(p1, p2 events.MarketPosition) int64 {
  2827  	// was long, still long (or 0)
  2828  	if p1.Size() > 0 && p2.Size() >= 0 && p2.Size() < p1.Size() {
  2829  		return p1.Size() - p2.Size()
  2830  	}
  2831  	// was short, still short (or 0)
  2832  	if p1.Size() < 0 && p2.Size() <= 0 && p2.Size() > p1.Size() {
  2833  		return p2.Size() - p1.Size()
  2834  	}
  2835  	// position changed side
  2836  	if p1.Size()*p2.Size() < 0 {
  2837  		if p1.Size() > 0 {
  2838  			return p1.Size()
  2839  		}
  2840  		return -p1.Size()
  2841  	}
  2842  	return 0
  2843  }
  2844  
  2845  func (m *Market) handleConfirmation(ctx context.Context, conf *types.OrderConfirmation, tradeT *types.TradeType) []*types.Order {
  2846  	// When re-submitting liquidity order, it happen that the pricing is putting
  2847  	// the order at a price which makes it uncross straight away.
  2848  	// then triggering this handleConfirmation flow, etc.
  2849  	// Although the order is considered aggressive, and we never expect in the flow
  2850  	// for an aggressive order to be pegged, so we never remove them from the pegged
  2851  	// list. All this impact the float of EnterAuction, which if triggered from there
  2852  	// will try to park all pegged orders, including this order which have never been
  2853  	// removed from the pegged list. We add this check to make sure  that if the
  2854  	// aggressive order is pegged, we then do remove it from the list.
  2855  	if conf.Order.PeggedOrder != nil {
  2856  		if conf.Order.Remaining == 0 || conf.Order.Status != types.OrderStatusActive {
  2857  			m.removePeggedOrder(conf.Order)
  2858  		}
  2859  	}
  2860  
  2861  	m.handleConfirmationPassiveOrders(ctx, conf)
  2862  	orderUpdates := make([]*types.Order, 0, len(conf.PassiveOrdersAffected)+1)
  2863  	orderUpdates = append(orderUpdates, conf.Order)
  2864  	orderUpdates = append(orderUpdates, conf.PassiveOrdersAffected...)
  2865  
  2866  	if len(conf.Trades) == 0 {
  2867  		return orderUpdates
  2868  	}
  2869  	m.setLastTradedPrice(conf.Trades[len(conf.Trades)-1])
  2870  
  2871  	// Insert all trades resulted from the executed order
  2872  	tradeEvts := make([]events.Event, 0, len(conf.Trades))
  2873  	tradedValue, _ := num.UintFromDecimal(
  2874  		conf.TradedValue().ToDecimal().Div(m.positionFactor))
  2875  	for idx, trade := range conf.Trades {
  2876  		trade.SetIDs(m.idgen.NextID(), conf.Order, conf.PassiveOrdersAffected[idx])
  2877  		if tradeT != nil {
  2878  			trade.Type = *tradeT
  2879  		} else {
  2880  			m.markPriceCalculator.NewTrade(trade)
  2881  			if m.internalCompositePriceCalculator != nil {
  2882  				m.internalCompositePriceCalculator.NewTrade(trade)
  2883  			}
  2884  		}
  2885  
  2886  		tradeEvts = append(tradeEvts, events.NewTradeEvent(ctx, *trade))
  2887  		notionalTraded, _ := num.UintFromDecimal(num.UintZero().Mul(num.NewUint(trade.Size), trade.Price).ToDecimal().Div(m.positionFactor))
  2888  		m.marketActivityTracker.RecordNotionalTraded(m.settlementAsset, m.mkt.ID, notionalTraded)
  2889  
  2890  		preTradePositions := m.position.GetPositionsByParty(trade.Buyer, trade.Seller)
  2891  		for i, mp := range m.position.Update(ctx, trade, conf.PassiveOrdersAffected[idx], conf.Order) {
  2892  			m.marketActivityTracker.RecordPosition(m.settlementAsset, mp.Party(), m.mkt.ID, mp.Size(), trade.Price, m.positionFactor, m.timeService.GetTimeNow())
  2893  			if closedPosition := decreasedPosition(preTradePositions[i], mp); closedPosition > 0 {
  2894  				var reaslisedPosition num.Decimal
  2895  				if preTradePositions[i].Size() > 0 {
  2896  					// a party **reduces** their **LONG** position
  2897  					// (trade price - average entry price) * position delta$$
  2898  					reaslisedPosition = trade.Price.ToDecimal().Sub(preTradePositions[i].AverageEntryPrice().ToDecimal()).Mul(num.DecimalFromInt64(closedPosition)).Div(m.positionFactor)
  2899  				} else {
  2900  					// a party **reduces** their **SHORT** position
  2901  					// (average entry price - trade price) * position delta$$
  2902  					reaslisedPosition = preTradePositions[i].AverageEntryPrice().ToDecimal().Sub(trade.Price.ToDecimal()).Mul(num.DecimalFromInt64(closedPosition)).Div(m.positionFactor)
  2903  				}
  2904  				m.marketActivityTracker.RecordRealisedPosition(m.GetSettlementAsset(), mp.Party(), m.mkt.ID, reaslisedPosition)
  2905  			}
  2906  		}
  2907  		// if the passive party is in isolated margin we need to update the margin on the position change
  2908  		if m.getMarginMode(conf.PassiveOrdersAffected[idx].Party) == types.MarginModeIsolatedMargin {
  2909  			pos, _ := m.position.GetPositionByPartyID(conf.PassiveOrdersAffected[idx].Party)
  2910  			err := m.updateIsolatedMarginsOnPositionChange(ctx, pos, conf.PassiveOrdersAffected[idx], trade)
  2911  			if err != nil {
  2912  				// if the evaluation after the position update means the party has insufficient funds, all of their orders need to be stopped
  2913  				// but first we need to transfer the margins from the order margin account.
  2914  				if err == risk.ErrInsufficientFundsForMaintenanceMargin {
  2915  					m.handleIsolatedMarginInsufficientOrderMargin(ctx, conf.PassiveOrdersAffected[idx].Party)
  2916  				}
  2917  				m.log.Error("failed to update isolated margin on position change", logging.Error(err))
  2918  			}
  2919  		}
  2920  		// if we're uncrossing an auction then we need to do this also for parties with isolated margin on the "aggressive" side
  2921  		if m.as.InAuction() {
  2922  			aggressor := conf.Order.Party
  2923  			if m.getMarginMode(aggressor) == types.MarginModeIsolatedMargin {
  2924  				aggressorOrder := conf.Order
  2925  				pos, _ := m.position.GetPositionByPartyID(aggressor)
  2926  				err := m.updateIsolatedMarginsOnPositionChange(ctx, pos, aggressorOrder, trade)
  2927  				if err != nil {
  2928  					m.log.Error("failed to update isolated margin on position change", logging.Error(err))
  2929  					if err == risk.ErrInsufficientFundsForMaintenanceMargin {
  2930  						m.handleIsolatedMarginInsufficientOrderMargin(ctx, aggressor)
  2931  					}
  2932  				}
  2933  			}
  2934  		}
  2935  		// Record open interest change
  2936  		if err := m.tsCalc.RecordOpenInterest(m.position.GetOpenInterest(), m.timeService.GetTimeNow()); err != nil {
  2937  			m.log.Debug("unable record open interest",
  2938  				logging.String("market-id", m.GetID()),
  2939  				logging.Error(err))
  2940  		}
  2941  		// add trade to settlement engine for correct MTM settlement of individual trades
  2942  		m.settlement.AddTrade(trade)
  2943  	}
  2944  	if !m.as.InAuction() {
  2945  		aggressor := conf.Order.Party
  2946  		if quantum, err := m.collateral.GetAssetQuantum(m.settlementAsset); err == nil && !quantum.IsZero() {
  2947  			n, _ := num.UintFromDecimal(tradedValue.ToDecimal().Div(quantum))
  2948  			m.marketActivityTracker.RecordNotionalTakerVolume(m.mkt.ID, aggressor, n)
  2949  		}
  2950  	}
  2951  	m.feeSplitter.AddTradeValue(tradedValue)
  2952  	m.marketActivityTracker.AddValueTraded(m.settlementAsset, m.mkt.ID, tradedValue)
  2953  	m.broker.SendBatch(tradeEvts)
  2954  
  2955  	// check reference moves if we have order updates, and we are not in an auction (or leaving an auction)
  2956  	// we handle reference moves in confirmMTM when leaving an auction already
  2957  	if len(orderUpdates) > 0 && !m.as.CanLeave() && !m.as.InAuction() {
  2958  		m.checkForReferenceMoves(
  2959  			ctx, orderUpdates, false)
  2960  	}
  2961  
  2962  	return orderUpdates
  2963  }
  2964  
  2965  // confirmMTM returns false if the MTM settlement was skipped due to price cap.
  2966  func (m *Market) confirmMTM(ctx context.Context, skipMargin bool) bool {
  2967  	// now let's get the transfers for MTM settlement
  2968  	mp := m.getCurrentMarkPrice()
  2969  	// if this is a capped market with a max price, skip MTM until the mark price is within the [0,max_price] range.
  2970  	if m.capMax != nil && m.capMax.LT(mp) {
  2971  		return false
  2972  	}
  2973  	m.liquidation.UpdateMarkPrice(mp.Clone())
  2974  	evts := m.position.UpdateMarkPrice(mp)
  2975  	settle := m.settlement.SettleMTM(ctx, mp, evts)
  2976  
  2977  	for _, t := range settle {
  2978  		m.recordPositionActivity(t.Transfer())
  2979  	}
  2980  
  2981  	// Only process collateral and risk once per order, not for every trade
  2982  	margins, isolatedMarginPartiesToClose := m.collateralAndRisk(ctx, settle)
  2983  	orderUpdates := m.handleRiskEvts(ctx, margins, isolatedMarginPartiesToClose)
  2984  
  2985  	// orders updated -> check reference moves
  2986  	// force check
  2987  	m.checkForReferenceMoves(ctx, orderUpdates, false)
  2988  
  2989  	if !skipMargin {
  2990  		// release excess margin for all positions
  2991  		m.recheckMargin(ctx, m.position.Positions())
  2992  	}
  2993  
  2994  	// tell the AMM engine we've MTM'd so any closing pool's can be cancelled
  2995  	m.amm.OnMTM(ctx)
  2996  	return true
  2997  }
  2998  
  2999  func (m *Market) handleRiskEvts(ctx context.Context, margins []events.Risk, isolatedMargin []events.Risk) []*types.Order {
  3000  	if len(margins) == 0 {
  3001  		return nil
  3002  	}
  3003  	isolatedForCloseout := m.collateral.IsolatedMarginUpdate(isolatedMargin)
  3004  	transfers, closed, bondPenalties, err := m.collateral.MarginUpdate(ctx, m.GetID(), margins)
  3005  	if err != nil {
  3006  		m.log.Error("margin update had issues", logging.Error(err))
  3007  	}
  3008  	if err == nil && len(transfers) > 0 {
  3009  		evt := events.NewLedgerMovements(ctx, transfers)
  3010  		m.broker.Send(evt)
  3011  	}
  3012  	if len(bondPenalties) > 0 {
  3013  		transfers, err := m.bondSlashing(ctx, bondPenalties...)
  3014  		if err != nil {
  3015  			m.log.Error("Failed to perform bond slashing",
  3016  				logging.Error(err))
  3017  		}
  3018  		// if bond slashing occurred then amounts in "closed" will not be accurate
  3019  		if len(transfers) > 0 {
  3020  			m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  3021  			closedRecalculated := make([]events.Margin, 0, len(closed))
  3022  			for _, c := range closed {
  3023  				if pos, ok := m.position.GetPositionByPartyID(c.Party()); ok {
  3024  					margin, err := m.collateral.GetPartyMargin(pos, m.settlementAsset, m.mkt.ID)
  3025  					if err != nil {
  3026  						m.log.Error("couldn't get party margin",
  3027  							logging.PartyID(c.Party()),
  3028  							logging.Error(err))
  3029  						// keep old value if we weren't able to recalculate
  3030  						closedRecalculated = append(closedRecalculated, c)
  3031  						continue
  3032  					}
  3033  					closedRecalculated = append(closedRecalculated, margin)
  3034  				}
  3035  			}
  3036  			closed = closedRecalculated
  3037  		}
  3038  	}
  3039  
  3040  	closed = append(closed, isolatedForCloseout...)
  3041  	if len(closed) == 0 {
  3042  		m.updateLiquidityFee(ctx)
  3043  		return nil
  3044  	}
  3045  	var orderUpdates []*types.Order
  3046  	upd := m.resolveClosedOutParties(ctx, closed)
  3047  	if len(upd) > 0 {
  3048  		orderUpdates = append(orderUpdates, upd...)
  3049  	}
  3050  
  3051  	m.updateLiquidityFee(ctx)
  3052  	return orderUpdates
  3053  }
  3054  
  3055  // updateLiquidityFee computes the current LiquidityProvision fee and updates
  3056  // the fee engine.
  3057  func (m *Market) updateLiquidityFee(ctx context.Context) {
  3058  	provisions := m.liquidityEngine.ProvisionsPerParty().Clone()
  3059  	for party, pool := range m.amm.GetAMMPoolsBySubAccount() {
  3060  		provisions[party] = &types.LiquidityProvision{
  3061  			Party:            party,
  3062  			CommitmentAmount: pool.CommitmentAmount(),
  3063  			Fee:              pool.LiquidityFee(),
  3064  		}
  3065  	}
  3066  
  3067  	var fee num.Decimal
  3068  	switch m.mkt.Fees.LiquidityFeeSettings.Method {
  3069  	case types.LiquidityFeeMethodConstant:
  3070  		if len(provisions) != 0 {
  3071  			fee = m.mkt.Fees.LiquidityFeeSettings.FeeConstant
  3072  		}
  3073  	case types.LiquidityFeeMethodMarginalCost:
  3074  		fee = provisions.FeeForTarget(m.getTargetStake())
  3075  	case types.LiquidityFeeMethodWeightedAverage:
  3076  		fee = provisions.FeeForWeightedAverage()
  3077  	default:
  3078  		m.log.Panic("unknown liquidity fee method")
  3079  	}
  3080  
  3081  	if !fee.Equals(m.getLiquidityFee()) {
  3082  		m.fee.SetLiquidityFee(fee)
  3083  		m.setLiquidityFee(fee)
  3084  		m.broker.Send(
  3085  			events.NewMarketUpdatedEvent(ctx, *m.mkt),
  3086  		)
  3087  	}
  3088  }
  3089  
  3090  func (m *Market) setLiquidityFee(fee num.Decimal) {
  3091  	m.mkt.Fees.Factors.LiquidityFee = fee
  3092  }
  3093  
  3094  func (m *Market) getLiquidityFee() num.Decimal {
  3095  	return m.mkt.Fees.Factors.LiquidityFee
  3096  }
  3097  
  3098  // resolveClosedOutParties - the parties with the given market position who haven't got sufficient collateral
  3099  // need to be closed out -> the network buys/sells the open volume, and trades with the rest of the network
  3100  // this flow is similar to the SubmitOrder bit where trades are made, with fewer checks (e.g. no MTM settlement, no risk checks)
  3101  // pass in the order which caused parties to be distressed.
  3102  func (m *Market) resolveClosedOutParties(ctx context.Context, distressedMarginEvts []events.Margin) []*types.Order {
  3103  	if len(distressedMarginEvts) == 0 {
  3104  		return nil
  3105  	}
  3106  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "resolveClosedOutParties")
  3107  	defer timer.EngineTimeCounterAdd()
  3108  
  3109  	now := m.timeService.GetTimeNow()
  3110  	// this is going to be run after the closed out routines
  3111  	// are finished, in order to notify the liquidity engine of
  3112  	// any changes in the book
  3113  	orderUpdates := []*types.Order{}
  3114  
  3115  	distressedPos := make([]events.MarketPosition, 0, len(distressedMarginEvts))
  3116  	for _, v := range distressedMarginEvts {
  3117  		if m.log.GetLevel() == logging.DebugLevel {
  3118  			m.log.Debug("closing out party",
  3119  				logging.PartyID(v.Party()),
  3120  				logging.MarketID(m.GetID()))
  3121  		}
  3122  		// we're not removing orders for isolated margin closed out parties
  3123  		if m.getMarginMode(v.Party()) == types.MarginModeCrossMargin {
  3124  			distressedPos = append(distressedPos, v)
  3125  		}
  3126  	}
  3127  
  3128  	rmorders, err := m.matching.RemoveDistressedOrders(distressedPos)
  3129  	if err != nil {
  3130  		m.log.Panic("Failed to remove distressed parties from the orderbook",
  3131  			logging.Error(err),
  3132  		)
  3133  	}
  3134  
  3135  	mktID := m.GetID()
  3136  	// push rm orders into buf
  3137  	// and remove the orders from the positions engine
  3138  	evts := []events.Event{}
  3139  	for _, o := range rmorders {
  3140  		if o.IsExpireable() {
  3141  			m.expiringOrders.RemoveOrder(o.ExpiresAt, o.ID)
  3142  		}
  3143  		if o.PeggedOrder != nil {
  3144  			m.removePeggedOrder(o)
  3145  		}
  3146  		o.UpdatedAt = now.UnixNano()
  3147  		evts = append(evts, events.NewOrderEvent(ctx, o))
  3148  		_ = m.position.UnregisterOrder(ctx, o)
  3149  	}
  3150  
  3151  	// add the orders remove from the book to the orders
  3152  	// to be sent to the liquidity engine
  3153  	orderUpdates = append(orderUpdates, rmorders...)
  3154  
  3155  	// now we also remove ALL parked order for the different parties
  3156  	for _, v := range distressedPos {
  3157  		orders, oevts := m.peggedOrders.RemoveAllForParty(
  3158  			ctx, v.Party(), types.OrderStatusStopped)
  3159  
  3160  		for _, v := range orders {
  3161  			m.expiringOrders.RemoveOrder(v.ExpiresAt, v.ID)
  3162  		}
  3163  
  3164  		// add all pegged orders too to the orderUpdates
  3165  		orderUpdates = append(orderUpdates, orders...)
  3166  		// add all events to evts list
  3167  		evts = append(evts, oevts...)
  3168  	}
  3169  
  3170  	// send all orders which got stopped through the event bus
  3171  	m.broker.SendBatch(evts)
  3172  
  3173  	closed := distressedMarginEvts // default behaviour (ie if rmorders is empty) is to closed out all distressed positions we started out with
  3174  
  3175  	// we need to check margin requirements again, it's possible for parties to no longer be distressed now that their orders have been removed
  3176  	if len(rmorders) != 0 {
  3177  		var okPos []events.Margin // need to declare this because we want to reassign closed
  3178  		// now that we closed orders, let's run the risk engine again
  3179  		// so it'll separate the positions still in distress from the
  3180  		// which have acceptable margins
  3181  		increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano())
  3182  		var pr *num.Uint
  3183  		if m.capMax != nil && m.fCap.FullyCollateralised {
  3184  			pr = m.capMax.Clone()
  3185  		} else {
  3186  			pr = m.lastTradedPrice.Clone()
  3187  		}
  3188  		okPos, closed = m.risk.ExpectMargins(distressedMarginEvts, pr, increment, m.getAuctionPrice())
  3189  
  3190  		parties := make([]string, 0, len(okPos))
  3191  		for _, v := range okPos {
  3192  			parties = append(parties, v.Party())
  3193  		}
  3194  		if m.log.IsDebug() {
  3195  			for _, pID := range parties {
  3196  				m.log.Debug("previously distressed party have now an acceptable margin",
  3197  					logging.String("market-id", mktID),
  3198  					logging.String("party-id", pID))
  3199  			}
  3200  		}
  3201  		if len(parties) > 0 {
  3202  			// emit event indicating we had to close orders, but parties were not distressed anymore after doing so.
  3203  			m.broker.Send(events.NewDistressedOrdersEvent(ctx, mktID, parties))
  3204  		}
  3205  	}
  3206  
  3207  	// if no position are meant to be closed, just return now.
  3208  	if len(closed) <= 0 {
  3209  		return orderUpdates
  3210  	}
  3211  
  3212  	currentMP := m.getCurrentMarkPrice()
  3213  	mCmp := m.priceToMarketPrecision(currentMP)
  3214  	closedMPs, closedParties, _ := m.liquidation.ClearDistressedParties(ctx, m.idgen, closed, currentMP, mCmp)
  3215  	dp, sp := m.position.MarkDistressed(closedParties)
  3216  	if len(dp) > 0 || len(sp) > 0 {
  3217  		m.broker.Send(events.NewDistressedPositionsEvent(ctx, m.GetID(), dp, sp))
  3218  	}
  3219  	m.finalizePartiesCloseOut(ctx, closed, closedMPs)
  3220  	m.zeroOutNetwork(ctx, closedParties)
  3221  	return orderUpdates
  3222  }
  3223  
  3224  func (m *Market) finalizePartiesCloseOut(
  3225  	ctx context.Context,
  3226  	closed []events.Margin,
  3227  	closedMPs []events.MarketPosition,
  3228  ) {
  3229  	// remove accounts, positions and return
  3230  	// from settlement engine first
  3231  	m.settlement.RemoveDistressed(ctx, closed)
  3232  	// then from positions
  3233  	toRemoveFromPosition := []events.MarketPosition{}
  3234  	for _, mp := range closedMPs {
  3235  		if m.getMarginMode(mp.Party()) == types.MarginModeCrossMargin || (mp.Buy() == 0 && mp.Sell() == 0) {
  3236  			toRemoveFromPosition = append(toRemoveFromPosition, mp)
  3237  		}
  3238  		var reaslisedPosition num.Decimal
  3239  		if mp.Size() > 0 {
  3240  			// a party **closed out** on their **LONG** position
  3241  			// (trade price - average entry price) * position delta$$
  3242  			reaslisedPosition = m.getCurrentMarkPrice().ToDecimal().Sub(mp.AverageEntryPrice().ToDecimal()).Mul(num.DecimalFromInt64(mp.Size())).Div(m.positionFactor)
  3243  		} else {
  3244  			// a party **closed out** their **SHORT** position
  3245  			// (average entry price - trade price) * position delta$$
  3246  			reaslisedPosition = mp.AverageEntryPrice().ToDecimal().Sub(m.getCurrentMarkPrice().ToDecimal()).Mul(num.DecimalFromInt64(-mp.Size())).Div(m.positionFactor)
  3247  		}
  3248  		m.marketActivityTracker.RecordRealisedPosition(m.settlementAsset, mp.Party(), m.mkt.ID, reaslisedPosition)
  3249  	}
  3250  	m.position.RemoveDistressed(toRemoveFromPosition)
  3251  	// but we want to update the market activity tracker on their 0 position for all of the closed parties
  3252  	for _, mp := range closedMPs {
  3253  		// record the updated closed out party's position
  3254  		m.marketActivityTracker.RecordPosition(m.settlementAsset, mp.Party(), m.mkt.ID, 0, mp.Price(), m.positionFactor, m.timeService.GetTimeNow())
  3255  	}
  3256  
  3257  	// if the distressed party was an AMM we need to stop it AMM-ing
  3258  	m.amm.RemoveDistressed(ctx, closedMPs)
  3259  
  3260  	// finally remove from collateral (moving funds where needed)
  3261  	movements, err := m.collateral.RemoveDistressed(
  3262  		ctx, closedMPs, m.GetID(), m.settlementAsset, m.useGeneralAccountForMarginSearch)
  3263  	if err != nil {
  3264  		m.log.Panic(
  3265  			"Failed to remove distressed accounts cleanly",
  3266  			logging.Error(err))
  3267  	}
  3268  
  3269  	if len(movements.Entries) > 0 {
  3270  		m.broker.Send(
  3271  			events.NewLedgerMovements(
  3272  				ctx, []*types.LedgerMovement{movements}),
  3273  		)
  3274  	}
  3275  
  3276  	for _, mp := range closedMPs {
  3277  		if m.getMarginMode(mp.Party()) == types.MarginModeIsolatedMargin || (mp.Buy() != 0 && mp.Sell() != 0) {
  3278  			pp, _ := m.position.GetPositionByPartyID(mp.Party())
  3279  			if pp == nil {
  3280  				continue
  3281  			}
  3282  			marketObservable, evt, increment, _, marginFactor, orders, err := m.getIsolatedMarginContext(pp, nil)
  3283  			if err != nil {
  3284  				m.log.Panic("failed to get isolated margin context")
  3285  			}
  3286  			_, err = m.risk.CheckMarginInvariants(ctx, evt, marketObservable, increment, orders, marginFactor)
  3287  			if err == risk.ErrInsufficientFundsForOrderMargin {
  3288  				m.log.Debug("party in isolated margin mode has insufficient order margin", logging.String("party", mp.Party()))
  3289  				m.handleIsolatedMarginInsufficientOrderMargin(ctx, mp.Party())
  3290  			}
  3291  		}
  3292  	}
  3293  }
  3294  
  3295  func (m *Market) zeroOutNetwork(ctx context.Context, parties []string) {
  3296  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "zeroOutNetwork")
  3297  	defer timer.EngineTimeCounterAdd()
  3298  
  3299  	// ensure an original price is set
  3300  	marketID := m.GetID()
  3301  	now := m.timeService.GetTimeNow().UnixNano()
  3302  
  3303  	evts := make([]events.Event, 0, len(parties))
  3304  
  3305  	marginLevels := types.MarginLevels{
  3306  		MarketID:  marketID,
  3307  		Asset:     m.settlementAsset,
  3308  		Timestamp: now,
  3309  	}
  3310  	for _, p := range parties {
  3311  		marginLevels.Party = p
  3312  		marginLevels.MarginMode = m.getMarginMode(p)
  3313  		marginLevels.MarginFactor = m.getMarginFactor(p)
  3314  		if marginLevels.MarginMode == types.MarginModeIsolatedMargin {
  3315  			// for isolated margin closed out for position, there may still be a valid order margin
  3316  			marginLevels.OrderMargin = m.risk.CalcOrderMarginsForClosedOutParty(m.matching.GetOrdersPerParty(p), marginLevels.MarginFactor)
  3317  		}
  3318  		evts = append(evts, events.NewMarginLevelsEvent(ctx, marginLevels))
  3319  	}
  3320  	if len(evts) > 0 {
  3321  		m.broker.SendBatch(evts)
  3322  	}
  3323  }
  3324  
  3325  func (m *Market) recheckMargin(ctx context.Context, pos []events.MarketPosition) {
  3326  	posCrossMargin := make([]events.MarketPosition, 0, len(pos))
  3327  
  3328  	for _, mp := range pos {
  3329  		if m.getMarginMode(mp.Party()) == types.MarginModeCrossMargin {
  3330  			posCrossMargin = append(posCrossMargin, mp)
  3331  		}
  3332  	}
  3333  	risk := m.updateMargin(ctx, posCrossMargin)
  3334  	if len(risk) == 0 {
  3335  		return
  3336  	}
  3337  	// now transfer margins, ignore closed because we're only recalculating for non-distressed parties.
  3338  	m.transferRecheckMargins(ctx, risk)
  3339  }
  3340  
  3341  func (m *Market) checkMarginForOrder(ctx context.Context, pos *positions.MarketPosition, order *types.Order) error {
  3342  	risk, closed, err := m.calcMargins(ctx, pos, order)
  3343  	// margin error
  3344  	if err != nil {
  3345  		return err
  3346  	}
  3347  
  3348  	// margins calculated, set about tranferring funds. At this point, if closed is not empty, those parties are distressed
  3349  	// the risk slice are risk events, that we must use to transfer funds
  3350  	return m.transferMargins(ctx, risk, closed)
  3351  }
  3352  
  3353  // updateIsolatedMarginOnAggressor is called when a new or amended order is matched immediately upon submission.
  3354  // it checks that new margin requirements can be satisfied and if so transfers the margin from the general account to the margin account.
  3355  func (m *Market) updateIsolatedMarginOnAggressor(ctx context.Context, pos *positions.MarketPosition, order *types.Order, trades []*types.Trade, isAmend bool, fees *num.Uint) error {
  3356  	marketObservable, mpos, increment, _, marginFactor, orders, err := m.getIsolatedMarginContext(pos, order)
  3357  	if err != nil {
  3358  		return err
  3359  	}
  3360  
  3361  	totalTrades := uint64(0)
  3362  	for _, t := range trades {
  3363  		totalTrades += t.Size
  3364  	}
  3365  
  3366  	clonedOrders := make([]*types.Order, 0, len(orders))
  3367  	found := false
  3368  	for _, o := range orders {
  3369  		if o.ID == order.ID {
  3370  			clonedOrder := order.Clone()
  3371  			clonedOrder.Remaining -= totalTrades
  3372  			if clonedOrder.Remaining > 0 {
  3373  				clonedOrders = append(clonedOrders, clonedOrder)
  3374  			}
  3375  			found = true
  3376  		} else {
  3377  			clonedOrders = append(clonedOrders, o)
  3378  		}
  3379  	}
  3380  	if !found {
  3381  		clonedOrder := order.Clone()
  3382  		clonedOrder.Remaining -= totalTrades
  3383  		if clonedOrder.Remaining > 0 {
  3384  			clonedOrders = append(clonedOrders, clonedOrder)
  3385  		}
  3386  	}
  3387  
  3388  	aggressorFee := num.UintZero()
  3389  	if fees != nil {
  3390  		aggressorFee = fees.Clone()
  3391  	}
  3392  	risk, err := m.risk.UpdateIsolatedMarginOnAggressor(ctx, mpos, marketObservable, increment, clonedOrders, trades, marginFactor, order.Side, isAmend, aggressorFee)
  3393  	if err != nil {
  3394  		return err
  3395  	}
  3396  	if risk == nil {
  3397  		return nil
  3398  	}
  3399  	return m.transferMargins(ctx, risk, nil)
  3400  }
  3401  
  3402  func (m *Market) updateIsolatedMarginOnOrderCancel(ctx context.Context, mpos *positions.MarketPosition, order *types.Order) error {
  3403  	marketObservable, pos, increment, auctionPrice, marginFactor, orders, err := m.getIsolatedMarginContext(mpos, order)
  3404  	if err != nil {
  3405  		return err
  3406  	}
  3407  	risk, err := m.risk.UpdateIsolatedMarginOnOrderCancel(ctx, pos, orders, marketObservable, auctionPrice, increment, marginFactor)
  3408  	if err != nil {
  3409  		return err
  3410  	}
  3411  	if risk == nil {
  3412  		return nil
  3413  	}
  3414  	return m.transferMargins(ctx, []events.Risk{risk}, nil)
  3415  }
  3416  
  3417  func (m *Market) updateIsolatedMarginOnOrder(ctx context.Context, mpos *positions.MarketPosition, order *types.Order) error {
  3418  	marketObservable, pos, increment, auctionPrice, marginFactor, orders, err := m.getIsolatedMarginContext(mpos, order)
  3419  	if err != nil {
  3420  		return err
  3421  	}
  3422  	risk, err := m.risk.UpdateIsolatedMarginOnOrder(ctx, pos, orders, marketObservable, auctionPrice, increment, marginFactor)
  3423  	if err != nil {
  3424  		return err
  3425  	}
  3426  	if risk == nil {
  3427  		return nil
  3428  	}
  3429  	return m.transferMargins(ctx, []events.Risk{risk}, nil)
  3430  }
  3431  
  3432  func (m *Market) checkMarginForAmendOrder(ctx context.Context, existingOrder *types.Order, amendedOrder *types.Order) error {
  3433  	origPos, ok := m.position.GetPositionByPartyID(existingOrder.Party)
  3434  	if !ok {
  3435  		m.log.Panic("could not get position for party", logging.PartyID(existingOrder.Party))
  3436  	}
  3437  
  3438  	pos := origPos.Clone()
  3439  
  3440  	// if order was park we have nothing to do here
  3441  	if existingOrder.Status != types.OrderStatusParked {
  3442  		pos.UnregisterOrder(m.log, existingOrder)
  3443  	}
  3444  
  3445  	pos.RegisterOrder(m.log, amendedOrder)
  3446  
  3447  	// we are just checking here if we can pass the margin calls.
  3448  	_, _, err := m.calcMargins(ctx, pos, amendedOrder)
  3449  	return err
  3450  }
  3451  
  3452  func (m *Market) setLastTradedPrice(trade *types.Trade) {
  3453  	m.lastTradedPrice = trade.Price.Clone()
  3454  }
  3455  
  3456  // this function handles moving money after settle MTM + risk margin updates
  3457  // but does not move the money between party accounts (ie not to/from margin accounts after risk).
  3458  func (m *Market) collateralAndRisk(ctx context.Context, settle []events.Transfer) ([]events.Risk, []events.Risk) {
  3459  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "collateralAndRisk")
  3460  	defer timer.EngineTimeCounterAdd()
  3461  	evts, response, err := m.collateral.MarkToMarket(ctx, m.GetID(), settle, m.settlementAsset, m.useGeneralAccountForMarginSearch)
  3462  	if err != nil {
  3463  		m.log.Error(
  3464  			"Failed to process mark to market settlement (collateral)",
  3465  			logging.Error(err),
  3466  		)
  3467  		return nil, nil
  3468  	}
  3469  	// sending response to buffer
  3470  	if len(response) > 0 {
  3471  		m.broker.Send(events.NewLedgerMovements(ctx, response))
  3472  	}
  3473  
  3474  	// let risk engine do its thing here - it returns a slice of money that needs
  3475  	// to be moved to and from margin accounts
  3476  	increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano())
  3477  
  3478  	// split to cross and isolated margins to handle separately
  3479  	crossEvts := make([]events.Margin, 0, len(evts))
  3480  	isolatedEvts := make([]events.Margin, 0, len(evts))
  3481  	for _, evt := range evts {
  3482  		if m.getMarginMode(evt.Party()) == types.MarginModeCrossMargin {
  3483  			crossEvts = append(crossEvts, evt)
  3484  		} else {
  3485  			isolatedEvts = append(isolatedEvts, evt)
  3486  		}
  3487  	}
  3488  
  3489  	crossRiskUpdates := m.risk.UpdateMarginsOnSettlement(ctx, crossEvts, m.getCurrentMarkPriceForMargin(), increment, m.getAuctionPrice())
  3490  	isolatedMarginPartiesToClose := []events.Risk{}
  3491  	for _, evt := range isolatedEvts {
  3492  		mrgns, err := m.risk.CheckMarginInvariants(ctx, evt, m.getMarketObservable(nil), increment, m.matching.GetOrdersPerParty(evt.Party()), m.getMarginFactor(evt.Party()))
  3493  		if err == risk.ErrInsufficientFundsForMaintenanceMargin {
  3494  			m.log.Debug("party in isolated margin mode has insufficient margin", logging.String("party", evt.Party()))
  3495  			isolatedMarginPartiesToClose = append(isolatedMarginPartiesToClose, mrgns)
  3496  		}
  3497  	}
  3498  
  3499  	// if len(crossRiskUpdates) == 0 {
  3500  	// 	return nil, isolatedMarginPartiesToClose
  3501  	// }
  3502  	return crossRiskUpdates, isolatedMarginPartiesToClose
  3503  }
  3504  
  3505  func (m *Market) CancelAllStopOrders(ctx context.Context, partyID string) error {
  3506  	if !m.canTrade() {
  3507  		return common.ErrTradingNotAllowed
  3508  	}
  3509  
  3510  	stopOrders, err := m.stopOrders.Cancel(partyID, "")
  3511  	if err != nil {
  3512  		return err
  3513  	}
  3514  
  3515  	m.removeCancelledExpiringStopOrders(stopOrders)
  3516  
  3517  	evts := make([]events.Event, 0, len(stopOrders))
  3518  	for _, v := range stopOrders {
  3519  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  3520  	}
  3521  
  3522  	m.broker.SendBatch(evts)
  3523  
  3524  	return nil
  3525  }
  3526  
  3527  func (m *Market) CancelAllOrders(ctx context.Context, partyID string) ([]*types.OrderCancellationConfirmation, error) {
  3528  	defer m.onTxProcessed()
  3529  
  3530  	if !m.canTrade() {
  3531  		return nil, common.ErrTradingNotAllowed
  3532  	}
  3533  
  3534  	// get all order for this party in the book
  3535  	orders := m.matching.GetOrdersPerParty(partyID)
  3536  
  3537  	// add all orders being eventually parked
  3538  	orders = append(orders, m.peggedOrders.GetAllParkedForParty(partyID)...)
  3539  
  3540  	// just an early exit, there's just no orders...
  3541  	if len(orders) <= 0 {
  3542  		return nil, nil
  3543  	}
  3544  
  3545  	// now we eventually dedup them
  3546  	uniq := map[string]*types.Order{}
  3547  	for _, v := range orders {
  3548  		uniq[v.ID] = v
  3549  	}
  3550  
  3551  	// put them back in the slice, and sort them
  3552  	orders = make([]*types.Order, 0, len(uniq))
  3553  	for _, v := range uniq {
  3554  		orders = append(orders, v)
  3555  	}
  3556  	sort.Slice(orders, func(i, j int) bool {
  3557  		return orders[i].ID < orders[j].ID
  3558  	})
  3559  
  3560  	cancellations := make([]*types.OrderCancellationConfirmation, 0, len(orders))
  3561  	orderIDs := make([]string, 0, len(orders))
  3562  
  3563  	// now iterate over all orders and cancel one by one.
  3564  	cancelledOrders := make([]*types.Order, 0, len(orders))
  3565  	for _, order := range orders {
  3566  		cancellation, err := m.cancelOrderInBatch(ctx, partyID, order.ID)
  3567  		if err != nil {
  3568  			return nil, err
  3569  		}
  3570  		orderIDs = append(orderIDs, order.ID)
  3571  		cancellations = append(cancellations, cancellation)
  3572  		cancelledOrders = append(cancelledOrders, cancellation.Order)
  3573  	}
  3574  	cancelEvt := events.NewCancelledOrdersEvent(ctx, m.GetID(), partyID, orderIDs...)
  3575  	m.broker.Send(cancelEvt)
  3576  	// we have just cancelled all orders, release excess margin
  3577  	m.releaseMarginExcess(ctx, partyID)
  3578  
  3579  	m.checkForReferenceMoves(ctx, cancelledOrders, false)
  3580  
  3581  	return cancellations, nil
  3582  }
  3583  
  3584  func (m *Market) CancelOrder(
  3585  	ctx context.Context,
  3586  	partyID, orderID string, deterministicID string,
  3587  ) (oc *types.OrderCancellationConfirmation, _ error) {
  3588  	idgen := idgeneration.New(deterministicID)
  3589  	return m.CancelOrderWithIDGenerator(ctx, partyID, orderID, idgen)
  3590  }
  3591  
  3592  func (m *Market) CancelOrderWithIDGenerator(
  3593  	ctx context.Context,
  3594  	partyID, orderID string,
  3595  	idgen common.IDGenerator,
  3596  ) (oc *types.OrderCancellationConfirmation, _ error) {
  3597  	defer m.onTxProcessed()
  3598  
  3599  	m.idgen = idgen
  3600  	defer func() { m.idgen = nil }()
  3601  
  3602  	if !m.canTrade() {
  3603  		return nil, common.ErrTradingNotAllowed
  3604  	}
  3605  
  3606  	conf, err := m.cancelOrder(ctx, partyID, orderID)
  3607  	if err != nil {
  3608  		return conf, err
  3609  	}
  3610  
  3611  	if !m.as.InAuction() {
  3612  		m.checkForReferenceMoves(ctx, []*types.Order{conf.Order}, false)
  3613  	}
  3614  
  3615  	return conf, nil
  3616  }
  3617  
  3618  func (m *Market) CancelStopOrder(
  3619  	ctx context.Context,
  3620  	partyID, orderID string,
  3621  ) error {
  3622  	if !m.canTrade() {
  3623  		return common.ErrTradingNotAllowed
  3624  	}
  3625  
  3626  	stopOrders, err := m.stopOrders.Cancel(partyID, orderID)
  3627  	if err != nil || len(stopOrders) <= 0 { // could return just an empty slice
  3628  		return err
  3629  	}
  3630  
  3631  	m.removeCancelledExpiringStopOrders(stopOrders)
  3632  
  3633  	evts := []events.Event{events.NewStopOrderEvent(ctx, stopOrders[0])}
  3634  	if len(stopOrders) > 1 {
  3635  		evts = append(evts, events.NewStopOrderEvent(ctx, stopOrders[1]))
  3636  	}
  3637  
  3638  	m.broker.SendBatch(evts)
  3639  
  3640  	return nil
  3641  }
  3642  
  3643  func (m *Market) removeCancelledExpiringStopOrders(
  3644  	stopOrders []*types.StopOrder,
  3645  ) {
  3646  	for _, o := range stopOrders {
  3647  		if o.Expiry.Expires() {
  3648  			m.expiringStopOrders.RemoveOrder(o.Expiry.ExpiresAt.UnixNano(), o.ID)
  3649  		}
  3650  	}
  3651  }
  3652  
  3653  func (m *Market) cancelOrderInBatch(ctx context.Context, partyID string, orderID string) (*types.OrderCancellationConfirmation, error) {
  3654  	return m.cancelSingleOrder(ctx, partyID, orderID, true)
  3655  }
  3656  
  3657  // CancelOrder cancels the given order.
  3658  func (m *Market) cancelOrder(ctx context.Context, partyID, orderID string) (*types.OrderCancellationConfirmation, error) {
  3659  	return m.cancelSingleOrder(ctx, partyID, orderID, false)
  3660  }
  3661  
  3662  func (m *Market) cancelSingleOrder(ctx context.Context, partyID, orderID string, isBatch bool) (*types.OrderCancellationConfirmation, error) {
  3663  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "CancelOrder")
  3664  	defer timer.EngineTimeCounterAdd()
  3665  
  3666  	if m.closed {
  3667  		return nil, common.ErrMarketClosed
  3668  	}
  3669  
  3670  	order, foundOnBook, err := m.getOrderByID(orderID)
  3671  	if err != nil {
  3672  		return nil, err
  3673  	}
  3674  
  3675  	// Only allow the original order creator to cancel their order
  3676  	if order.Party != partyID {
  3677  		if m.log.GetLevel() == logging.DebugLevel {
  3678  			m.log.Debug("Party ID mismatch",
  3679  				logging.String("party-id", partyID),
  3680  				logging.String("order-id", orderID),
  3681  				logging.String("market", m.mkt.ID))
  3682  		}
  3683  		return nil, types.ErrInvalidPartyID
  3684  	}
  3685  
  3686  	if !isBatch {
  3687  		defer m.releaseMarginExcess(ctx, partyID)
  3688  	}
  3689  
  3690  	if foundOnBook {
  3691  		cancellation, err := m.matching.CancelOrder(order)
  3692  		if cancellation == nil || err != nil {
  3693  			if m.log.GetLevel() == logging.DebugLevel {
  3694  				m.log.Debug("Failure after cancel order from matching engine",
  3695  					logging.String("party-id", partyID),
  3696  					logging.String("order-id", orderID),
  3697  					logging.String("market", m.mkt.ID),
  3698  					logging.Error(err))
  3699  			}
  3700  			return nil, err
  3701  		}
  3702  		_ = m.position.UnregisterOrder(ctx, order)
  3703  	}
  3704  
  3705  	if order.IsExpireable() {
  3706  		m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID)
  3707  	}
  3708  
  3709  	// If this is a pegged order, remove from pegged and parked lists
  3710  	if order.PeggedOrder != nil {
  3711  		m.removePeggedOrder(order)
  3712  		order.Status = types.OrderStatusCancelled
  3713  	}
  3714  
  3715  	// Publish the changed order details
  3716  	order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
  3717  	if !isBatch {
  3718  		m.broker.Send(events.NewCancelledOrdersEvent(ctx, order.MarketID, order.Party, order.ID))
  3719  	}
  3720  
  3721  	// if the order was found in the book and we're in isolated margin we need to update the
  3722  	// order margin
  3723  	if foundOnBook && m.getMarginMode(partyID) == types.MarginModeIsolatedMargin {
  3724  		pos, _ := m.position.GetPositionByPartyID(partyID)
  3725  		// it might be that we place orders before an auction, then during an auction we're trying to cancel the order - if we have still other order
  3726  		// they will definitely have insufficient order margin but that's ok, either they will be fine when uncrossing the auction
  3727  		// or will get cancelled then, no need to punish the party and cancel them at this point. Therefore this can either release funds
  3728  		// from the order account or error which we ignore.
  3729  		m.updateIsolatedMarginOnOrderCancel(ctx, pos, order)
  3730  	}
  3731  
  3732  	return &types.OrderCancellationConfirmation{Order: order}, nil
  3733  }
  3734  
  3735  // parkOrder removes the given order from the orderbook
  3736  // parkOrder will panic if it encounters errors, which means that it reached an
  3737  // invalid state.
  3738  func (m *Market) parkOrder(ctx context.Context, orderID string) *types.Order {
  3739  	order, err := m.matching.RemoveOrder(orderID)
  3740  	if err != nil {
  3741  		m.log.Panic("Failure to remove order from matching engine",
  3742  			logging.OrderID(orderID),
  3743  			logging.Error(err))
  3744  	}
  3745  
  3746  	_ = m.position.UnregisterOrder(ctx, order)
  3747  	m.peggedOrders.Park(order)
  3748  	m.broker.Send(events.NewOrderEvent(ctx, order))
  3749  	m.releaseMarginExcess(ctx, order.Party)
  3750  	return order
  3751  }
  3752  
  3753  // AmendOrder amend an existing order from the order book.
  3754  func (m *Market) AmendOrder(
  3755  	ctx context.Context,
  3756  	orderAmendment *types.OrderAmendment,
  3757  	party string,
  3758  	deterministicID string,
  3759  ) (oc *types.OrderConfirmation, _ error,
  3760  ) {
  3761  	idgen := idgeneration.New(deterministicID)
  3762  	return m.AmendOrderWithIDGenerator(ctx, orderAmendment, party, idgen)
  3763  }
  3764  
  3765  // handleIsolatedMarginInsufficientOrderMargin stops all party orders
  3766  // Upon position/order update if there are insufficient funds in the order margin, all open orders are stopped and margin re-evaluated.
  3767  func (m *Market) handleIsolatedMarginInsufficientOrderMargin(ctx context.Context, party string) {
  3768  	orders := m.matching.GetOrdersPerParty(party)
  3769  	for _, o := range orders {
  3770  		if !o.IsFinished() {
  3771  			m.matching.RemoveOrder(o.ID)
  3772  			m.unregisterAndReject(ctx, o, types.OrderErrorIsolatedMarginCheckFailed)
  3773  		}
  3774  		// TODO is there anywhere else that this order needs to be removed from?
  3775  	}
  3776  	pos, _ := m.position.GetPositionByPartyID(party)
  3777  	if err := m.updateIsolatedMarginOnOrder(ctx, pos, nil); err != nil {
  3778  		m.log.Panic("failed to release margin for party with insufficient order margin", logging.String("party", party))
  3779  	}
  3780  }
  3781  
  3782  func (m *Market) AmendOrderWithIDGenerator(
  3783  	ctx context.Context,
  3784  	orderAmendment *types.OrderAmendment,
  3785  	party string,
  3786  	idgen common.IDGenerator,
  3787  ) (oc *types.OrderConfirmation, _ error,
  3788  ) {
  3789  	defer m.onTxProcessed()
  3790  
  3791  	m.idgen = idgen
  3792  	defer func() { m.idgen = nil }()
  3793  
  3794  	defer func() {
  3795  		m.triggerStopOrders(ctx, idgen)
  3796  	}()
  3797  
  3798  	if !m.canTrade() {
  3799  		return nil, common.ErrTradingNotAllowed
  3800  	}
  3801  
  3802  	conf, updatedOrders, err := m.amendOrder(ctx, orderAmendment, party)
  3803  	if err != nil {
  3804  		m.log.Error("failed to amend order", logging.String("marketID", orderAmendment.MarketID), logging.String("orderID", orderAmendment.OrderID), logging.Error(err))
  3805  		if m.getMarginMode(party) == types.MarginModeIsolatedMargin && err == common.ErrMarginCheckFailed {
  3806  			m.handleIsolatedMarginInsufficientOrderMargin(ctx, party)
  3807  		}
  3808  		return nil, err
  3809  	}
  3810  
  3811  	allUpdatedOrders := append(
  3812  		[]*types.Order{conf.Order},
  3813  		conf.PassiveOrdersAffected...,
  3814  	)
  3815  	allUpdatedOrders = append(
  3816  		allUpdatedOrders,
  3817  		updatedOrders...,
  3818  	)
  3819  
  3820  	if !m.as.InAuction() {
  3821  		m.checkForReferenceMoves(ctx, allUpdatedOrders, false)
  3822  	}
  3823  
  3824  	return conf, nil
  3825  }
  3826  
  3827  func (m *Market) findOrderAndEnsureOwnership(
  3828  	orderID, partyID, marketID string,
  3829  ) (exitingOrder *types.Order, foundOnBook bool, err error) {
  3830  	// Try and locate the existing order specified on the
  3831  	// order book in the matching engine for this market
  3832  	existingOrder, foundOnBook, err := m.getOrderByID(orderID)
  3833  	if err != nil {
  3834  		if m.log.GetLevel() == logging.DebugLevel {
  3835  			m.log.Debug("Invalid order ID",
  3836  				logging.OrderID(orderID),
  3837  				logging.PartyID(partyID),
  3838  				logging.MarketID(marketID),
  3839  				logging.Error(err))
  3840  		}
  3841  		return nil, false, types.ErrInvalidOrderID
  3842  	}
  3843  
  3844  	// We can only amend this order if we created it
  3845  	if existingOrder.Party != partyID {
  3846  		if m.log.GetLevel() == logging.DebugLevel {
  3847  			m.log.Debug("Invalid party ID",
  3848  				logging.String("original party id:", existingOrder.Party),
  3849  				logging.PartyID(partyID),
  3850  			)
  3851  		}
  3852  		return nil, false, types.ErrInvalidPartyID
  3853  	}
  3854  
  3855  	// Validate Market
  3856  	if existingOrder.MarketID != marketID {
  3857  		// we should never reach this point
  3858  		m.log.Panic("Market ID mismatch",
  3859  			logging.MarketID(m.mkt.ID),
  3860  			logging.Order(*existingOrder),
  3861  			logging.Error(types.ErrInvalidMarketID),
  3862  		)
  3863  	}
  3864  
  3865  	return existingOrder, foundOnBook, err
  3866  }
  3867  
  3868  func (m *Market) amendOrder(
  3869  	ctx context.Context,
  3870  	orderAmendment *types.OrderAmendment,
  3871  	party string,
  3872  ) (cnf *types.OrderConfirmation, orderUpdates []*types.Order, returnedErr error) {
  3873  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "AmendOrder")
  3874  	defer timer.EngineTimeCounterAdd()
  3875  
  3876  	// Verify that the market is not closed
  3877  	if m.closed {
  3878  		return nil, nil, common.ErrMarketClosed
  3879  	}
  3880  
  3881  	existingOrder, foundOnBook, err := m.findOrderAndEnsureOwnership(
  3882  		orderAmendment.OrderID, party, m.GetID())
  3883  	if err != nil {
  3884  		return nil, nil, err
  3885  	}
  3886  
  3887  	if err := m.validateOrderAmendment(existingOrder, orderAmendment); err != nil {
  3888  		return nil, nil, err
  3889  	}
  3890  
  3891  	amendedOrder, err := existingOrder.ApplyOrderAmendment(orderAmendment, m.timeService.GetTimeNow().UnixNano(), m.priceFactor)
  3892  	if err != nil {
  3893  		return nil, nil, err
  3894  	}
  3895  
  3896  	if err := m.checkOrderAmendForSpam(amendedOrder); err != nil {
  3897  		return nil, nil, err
  3898  	}
  3899  
  3900  	if orderAmendment.Price != nil && amendedOrder.OriginalPrice != nil {
  3901  		if err = m.validateTickSize(amendedOrder.OriginalPrice); err != nil {
  3902  			return nil, nil, err
  3903  		}
  3904  	}
  3905  
  3906  	if err := m.position.ValidateAmendOrder(existingOrder, amendedOrder); err != nil {
  3907  		return nil, nil, err
  3908  	}
  3909  
  3910  	// We do this first, just in case the party would also have
  3911  	// change the expiry, and that would have been catched by
  3912  	// the follow up checks, so we do not insert a non-existing
  3913  	// order in the expiring orders
  3914  	// if remaining is reduces <= 0, then order is cancelled
  3915  	if amendedOrder.Remaining <= 0 {
  3916  		confirm, err := m.cancelOrder(
  3917  			ctx, existingOrder.Party, existingOrder.ID)
  3918  		if err != nil {
  3919  			return nil, nil, err
  3920  		}
  3921  		return &types.OrderConfirmation{
  3922  			Order: confirm.Order,
  3923  		}, nil, nil
  3924  	}
  3925  
  3926  	// If we have a pegged order that is no longer expiring, we need to remove it
  3927  	var (
  3928  		needToRemoveExpiry, needToAddExpiry bool
  3929  		expiresAt                           int64
  3930  	)
  3931  
  3932  	defer func() {
  3933  		// no errors, amend most likely happened properly
  3934  		if returnedErr == nil {
  3935  			if needToRemoveExpiry {
  3936  				m.expiringOrders.RemoveOrder(expiresAt, existingOrder.ID)
  3937  			}
  3938  			// need to make sure the order haven't been matched with the
  3939  			// amend, consuming the remain volume as well or we would
  3940  			// add an order while it's not needed to the expiring list
  3941  			if needToAddExpiry && cnf != nil && !cnf.Order.IsFinished() {
  3942  				m.expiringOrders.Insert(amendedOrder.ID, amendedOrder.ExpiresAt)
  3943  			}
  3944  		}
  3945  	}()
  3946  
  3947  	// if we are amending from GTT to GTC, flag ready to remove from expiry list
  3948  	if existingOrder.IsExpireable() && !amendedOrder.IsExpireable() {
  3949  		// We no longer need to handle the expiry
  3950  		needToRemoveExpiry = true
  3951  		expiresAt = existingOrder.ExpiresAt
  3952  	}
  3953  
  3954  	// if we are amending from GTC to GTT, flag ready to add to expiry list
  3955  	if !existingOrder.IsExpireable() && amendedOrder.IsExpireable() {
  3956  		// We need to handle the expiry
  3957  		needToAddExpiry = true
  3958  	}
  3959  
  3960  	// if both where expireable but we changed the duration
  3961  	// then we need to remove, then reinsert...
  3962  	if existingOrder.IsExpireable() && amendedOrder.IsExpireable() &&
  3963  		existingOrder.ExpiresAt != amendedOrder.ExpiresAt {
  3964  		// Still expiring but needs to be updated in the expiring
  3965  		// orders pool
  3966  		needToRemoveExpiry = true
  3967  		needToAddExpiry = true
  3968  		expiresAt = existingOrder.ExpiresAt
  3969  	}
  3970  
  3971  	// if expiration has changed and is before the original creation time, reject this amend
  3972  	if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < existingOrder.CreatedAt {
  3973  		if m.log.GetLevel() == logging.DebugLevel {
  3974  			m.log.Debug("Amended expiry before original creation time",
  3975  				logging.Int64("existing-created-at", existingOrder.CreatedAt),
  3976  				logging.Int64("amended-expires-at", amendedOrder.ExpiresAt),
  3977  				logging.Order(*existingOrder))
  3978  		}
  3979  		return nil, nil, types.ErrInvalidExpirationDatetime
  3980  	}
  3981  
  3982  	// if expiration has changed and is not 0, and is before currentTime
  3983  	// then we expire the order
  3984  	if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < amendedOrder.UpdatedAt {
  3985  		needToAddExpiry = false
  3986  		// remove the order from the expiring
  3987  		// at this point the order is still referenced at the time of expiry of the existingOrder
  3988  		if existingOrder.IsExpireable() {
  3989  			m.expiringOrders.RemoveOrder(existingOrder.ExpiresAt, amendedOrder.ID)
  3990  		}
  3991  
  3992  		// Update the existing message in place before we cancel it
  3993  		if foundOnBook {
  3994  			// Do not amend in place, the amend could be something
  3995  			// not supported for an amend in place, and not pass
  3996  			// the validation of the order book
  3997  			cancellation, err := m.matching.CancelOrder(existingOrder)
  3998  			if cancellation == nil || err != nil {
  3999  				m.log.Panic("Failure to cancel order from matching engine",
  4000  					logging.String("party-id", amendedOrder.Party),
  4001  					logging.String("order-id", amendedOrder.ID),
  4002  					logging.String("market", m.mkt.ID),
  4003  					logging.Error(err))
  4004  			}
  4005  
  4006  			// unregister the existing order
  4007  			_ = m.position.UnregisterOrder(ctx, existingOrder)
  4008  		}
  4009  
  4010  		// Update the order in our stores (will be marked as cancelled)
  4011  		// set the proper status
  4012  		amendedOrder.Status = types.OrderStatusExpired
  4013  		m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  4014  		m.removePeggedOrder(amendedOrder)
  4015  
  4016  		return &types.OrderConfirmation{
  4017  			Order: amendedOrder,
  4018  		}, nil, nil
  4019  	}
  4020  
  4021  	if existingOrder.PeggedOrder != nil {
  4022  		// Amend in place during an auction
  4023  		if m.as.InAuction() {
  4024  			ret := m.orderAmendWhenParked(amendedOrder)
  4025  			m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  4026  			return ret, nil, nil
  4027  		}
  4028  		err := m.repricePeggedOrder(amendedOrder)
  4029  		if err != nil {
  4030  			// Failed to reprice so we have to park the order
  4031  			if amendedOrder.Status != types.OrderStatusParked {
  4032  				// If we are live then park
  4033  				m.parkOrder(ctx, existingOrder.ID)
  4034  			}
  4035  			ret := m.orderAmendWhenParked(amendedOrder)
  4036  			m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  4037  			return ret, nil, nil
  4038  		}
  4039  		// We got a new valid price, if we are parked we need to unpark
  4040  		if amendedOrder.Status == types.OrderStatusParked {
  4041  			// is we cann pass the margin calls, then do nothing
  4042  			if err := m.checkMarginForAmendOrder(ctx, existingOrder, amendedOrder); err != nil {
  4043  				return nil, nil, err
  4044  			}
  4045  
  4046  			// we were parked, need to unpark
  4047  			m.peggedOrders.Unpark(amendedOrder.ID)
  4048  			return m.submitValidatedOrder(ctx, amendedOrder)
  4049  		}
  4050  	}
  4051  
  4052  	priceShift := amendedOrder.Price.NEQ(existingOrder.Price)
  4053  	sizeIncrease := amendedOrder.Size > existingOrder.Size
  4054  	sizeDecrease := amendedOrder.Size < existingOrder.Size
  4055  	expiryChange := amendedOrder.ExpiresAt != existingOrder.ExpiresAt
  4056  	timeInForceChange := amendedOrder.TimeInForce != existingOrder.TimeInForce
  4057  
  4058  	// If nothing changed, amend in place to update updatedAt and version number
  4059  	if !priceShift && !sizeIncrease && !sizeDecrease && !expiryChange && !timeInForceChange {
  4060  		ret := m.orderAmendInPlace(existingOrder, amendedOrder)
  4061  		m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  4062  		return ret, nil, nil
  4063  	}
  4064  
  4065  	// Update potential new position after the amend
  4066  	pos := m.position.AmendOrder(ctx, existingOrder, amendedOrder)
  4067  
  4068  	// Perform check and allocate margin if price or order size is increased
  4069  	// ignore rollback return here, as if we amend it means the order
  4070  	// is already on the book, not rollback will be needed, the margin
  4071  	// will be updated later on for sure.
  4072  
  4073  	// always update margin, even for price/size decrease
  4074  	if m.getMarginMode(party) == types.MarginModeCrossMargin {
  4075  		if err = m.checkMarginForOrder(ctx, pos, amendedOrder); err != nil {
  4076  			// Undo the position registering
  4077  			_ = m.position.AmendOrder(ctx, amendedOrder, existingOrder)
  4078  
  4079  			if m.log.GetLevel() == logging.DebugLevel {
  4080  				m.log.Debug("Unable to check/add margin for party",
  4081  					logging.String("market-id", m.GetID()),
  4082  					logging.Error(err))
  4083  			}
  4084  			return nil, nil, common.ErrMarginCheckFailed
  4085  		}
  4086  	}
  4087  
  4088  	icebergSizeIncrease := false
  4089  	if amendedOrder.IcebergOrder != nil && sizeIncrease {
  4090  		// iceberg orders size changes can always be done in-place because they either:
  4091  		// 1) decrease the size, which is already done in-place for all orders
  4092  		// 2) increase the size, which only increases the reserved remaining and not the "active" remaining of the iceberg
  4093  		// so we set an icebergSizeIncrease to skip the cancel-replace flow.
  4094  		sizeIncrease = false
  4095  		icebergSizeIncrease = true
  4096  	}
  4097  
  4098  	// if increase in size or change in price
  4099  	// ---> DO atomic cancel and submit
  4100  	if priceShift || sizeIncrease {
  4101  		return m.orderCancelReplace(ctx, existingOrder, amendedOrder)
  4102  	}
  4103  
  4104  	// if decrease in size or change in expiration date
  4105  	// ---> DO amend in place in matching engine
  4106  	if expiryChange || sizeDecrease || timeInForceChange || icebergSizeIncrease {
  4107  		ret := m.orderAmendInPlace(existingOrder, amendedOrder)
  4108  		if sizeDecrease {
  4109  			if m.getMarginMode(party) == types.MarginModeCrossMargin {
  4110  				// ensure we release excess if party reduced the size of their order
  4111  				m.recheckMargin(ctx, m.position.GetPositionsByParty(amendedOrder.Party))
  4112  			}
  4113  		}
  4114  		if m.getMarginMode(party) == types.MarginModeIsolatedMargin {
  4115  			pos, _ := m.position.GetPositionByPartyID(amendedOrder.Party)
  4116  			if err := m.updateIsolatedMarginOnOrder(ctx, pos, amendedOrder); err == risk.ErrInsufficientFundsForMarginInGeneralAccount {
  4117  				m.log.Error("party has insufficient margin to cover the order change, going to cancel all orders for the party")
  4118  				// in this case we're now with the position with the amended order and the order book with the amended
  4119  				// order which is consistent and will lead to the successful cancellation of all orders for the party.
  4120  				// the reason we can't amend back here in place is that while decreasing size etc can be done in place
  4121  				// increasing size (which is the inverse amend) cannot be done in place so we shouldn't attempt it.
  4122  				return nil, nil, common.ErrMarginCheckFailed
  4123  			}
  4124  		}
  4125  
  4126  		m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  4127  		return ret, nil, nil
  4128  	}
  4129  
  4130  	// we should never reach this point as amendment was validated before
  4131  	// and every kind should have been handled down here.
  4132  	m.log.Panic(
  4133  		"invalid amend did not match any amendment combination",
  4134  		logging.String("amended-order", amendedOrder.String()),
  4135  		logging.String("existing-order", amendedOrder.String()),
  4136  	)
  4137  
  4138  	return nil, nil, types.ErrEditNotAllowed
  4139  }
  4140  
  4141  func (m *Market) validateOrderAmendment(
  4142  	order *types.Order,
  4143  	amendment *types.OrderAmendment,
  4144  ) error {
  4145  	if err := amendment.Validate(); err != nil {
  4146  		return err
  4147  	}
  4148  	// check TIME_IN_FORCE and expiry
  4149  	if amendment.TimeInForce == types.OrderTimeInForceGTT {
  4150  		// if expiresAt is before or equal to created at
  4151  		// we return an error, we know ExpiresAt is set because of amendment.Validate
  4152  		if *amendment.ExpiresAt <= order.CreatedAt {
  4153  			return types.OrderErrorExpiryAtBeforeCreatedAt
  4154  		}
  4155  	}
  4156  
  4157  	if (amendment.TimeInForce == types.OrderTimeInForceGFN ||
  4158  		amendment.TimeInForce == types.OrderTimeInForceGFA) &&
  4159  		amendment.TimeInForce != order.TimeInForce {
  4160  		// We cannot amend to a GFA/GFN orders
  4161  		return types.OrderErrorCannotAmendToGFAOrGFN
  4162  	}
  4163  
  4164  	if (order.TimeInForce == types.OrderTimeInForceGFN ||
  4165  		order.TimeInForce == types.OrderTimeInForceGFA) &&
  4166  		(amendment.TimeInForce != order.TimeInForce &&
  4167  			amendment.TimeInForce != types.OrderTimeInForceUnspecified) {
  4168  		// We cannot amend from a GFA/GFN orders
  4169  		return types.OrderErrorCannotAmendFromGFAOrGFN
  4170  	}
  4171  	if order.PeggedOrder == nil {
  4172  		// We cannot change a pegged orders details on a non pegged order
  4173  		if amendment.PeggedOffset != nil ||
  4174  			amendment.PeggedReference != types.PeggedReferenceUnspecified {
  4175  			return types.OrderErrorCannotAmendPeggedOrderDetailsOnNonPeggedOrder
  4176  		}
  4177  	} else if amendment.Price != nil {
  4178  		// We cannot change the price on a pegged order
  4179  		return types.OrderErrorUnableToAmendPriceOnPeggedOrder
  4180  	}
  4181  	return nil
  4182  }
  4183  
  4184  func (m *Market) orderCancelReplace(
  4185  	ctx context.Context,
  4186  	existingOrder, newOrder *types.Order,
  4187  ) (conf *types.OrderConfirmation, orders []*types.Order, err error) {
  4188  	var fees events.FeesTransfer
  4189  
  4190  	defer func() {
  4191  		if err != nil && !(err == common.ErrMarginCheckFailed && conf != nil) {
  4192  			// if an error happens, the order never hit the book, so we can
  4193  			// just rollback the position size
  4194  			_ = m.position.AmendOrder(ctx, newOrder, existingOrder)
  4195  			// we have an error, but a non-nil confirmation object meaning we updated the book,
  4196  			// but the amend was rejected because of the margin check, we have to restore the book
  4197  			// to its original state
  4198  			return
  4199  		}
  4200  		if fees != nil {
  4201  			if feeErr := m.applyFees(ctx, newOrder, fees); feeErr != nil {
  4202  				m.log.Panic("orderCancelReplace failed to apply fees on order", logging.Order(newOrder), logging.String("aggressor total fees", fees.TotalFeesAmountPerParty()[newOrder.ID].String()), logging.Error(feeErr))
  4203  			}
  4204  		}
  4205  		orders = m.handleConfirmation(ctx, conf, nil)
  4206  		m.broker.Send(events.NewOrderEvent(ctx, conf.Order))
  4207  	}()
  4208  
  4209  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderCancelReplace")
  4210  	defer timer.EngineTimeCounterAdd()
  4211  
  4212  	// make sure the order is on the book, this was done by canceling the order initially, but that could
  4213  	// trigger an auction in some cases.
  4214  	if o, err := m.matching.GetOrderByID(existingOrder.ID); err != nil || o == nil {
  4215  		m.log.Panic("Can't CancelReplace, the original order was not found",
  4216  			logging.OrderWithTag(*existingOrder, "existing-order"),
  4217  			logging.Error(err))
  4218  	}
  4219  	// cancel-replace amend during auction is quite simple at this point
  4220  	if m.as.InAuction() {
  4221  		conf, err := m.matching.ReplaceOrder(existingOrder, newOrder)
  4222  		if err != nil {
  4223  			m.log.Panic("unable to submit order", logging.Error(err))
  4224  		}
  4225  		if newOrder.PeggedOrder != nil {
  4226  			m.log.Panic("should never reach this point")
  4227  		}
  4228  
  4229  		if m.getMarginMode(newOrder.Party) == types.MarginModeIsolatedMargin {
  4230  			pos, _ := m.position.GetPositionByPartyID(newOrder.Party)
  4231  			if err := m.updateIsolatedMarginOnOrder(ctx, pos, newOrder); err != nil {
  4232  				// existing order have status cancelled so we need to get it back to active so it can be stopped properly
  4233  				existingOrder.Status = newOrder.Status
  4234  				m.matching.ReplaceOrder(newOrder, existingOrder)
  4235  				if m.log.GetLevel() <= logging.DebugLevel {
  4236  					m.log.Debug("Unable to check/add margin for party",
  4237  						logging.Order(*newOrder), logging.Error(err))
  4238  				}
  4239  				return nil, nil, common.ErrMarginCheckFailed
  4240  			}
  4241  		}
  4242  		return conf, nil, nil
  4243  	}
  4244  
  4245  	// if its an iceberg order with a price change and it is being submitted aggressively
  4246  	// set the visible remaining to the full size
  4247  	if newOrder.IcebergOrder != nil {
  4248  		newOrder.Remaining += newOrder.IcebergOrder.ReservedRemaining
  4249  		newOrder.IcebergOrder.ReservedRemaining = 0
  4250  	}
  4251  
  4252  	// first we call the order book to evaluate auction triggers and get the list of trades
  4253  	trades, err := m.checkPriceAndGetTrades(ctx, newOrder)
  4254  	if err != nil {
  4255  		return nil, nil, errors.New("couldn't insert order in book")
  4256  	}
  4257  
  4258  	// try to apply fees on the trade
  4259  	if fees, err = m.calcFees(trades); err != nil {
  4260  		return nil, nil, errors.New("could not calculate fees for order")
  4261  	}
  4262  
  4263  	var aggressorFee *num.Uint
  4264  	if fees != nil {
  4265  		aggressorFee = fees.TotalFeesAmountPerParty()[newOrder.Party]
  4266  	}
  4267  
  4268  	marginMode := m.getMarginMode(newOrder.Party)
  4269  	pos, _ := m.position.GetPositionByPartyID(newOrder.Party)
  4270  	posWithTrades := pos
  4271  	if len(trades) > 0 {
  4272  		if marginMode == types.MarginModeIsolatedMargin {
  4273  			posWithTrades = pos.UpdateInPlaceOnTrades(m.log, newOrder.Side, trades, newOrder)
  4274  			if err = m.updateIsolatedMarginOnAggressor(ctx, posWithTrades, newOrder, trades, true, aggressorFee); err != nil {
  4275  				if m.log.GetLevel() <= logging.DebugLevel {
  4276  					m.log.Debug("Unable to check/add immediate trade margin for party",
  4277  						logging.Order(*newOrder), logging.Error(err))
  4278  				}
  4279  				return nil, nil, common.ErrMarginCheckFailed
  4280  			}
  4281  		} else if aggressorFee != nil {
  4282  			if err := m.collateral.PartyCanCoverFees(m.settlementAsset, m.mkt.ID, newOrder.Party, aggressorFee); err != nil {
  4283  				m.log.Error("insufficient funds to cover fees", logging.Order(newOrder), logging.Error(err))
  4284  				return nil, nil, err
  4285  			}
  4286  		}
  4287  	}
  4288  
  4289  	// "hot-swap" of the orders
  4290  	conf, err = m.matching.ReplaceOrder(existingOrder, newOrder)
  4291  	if err != nil {
  4292  		m.log.Panic("unable to submit order", logging.Error(err))
  4293  	}
  4294  
  4295  	// replace the trades in the confirmation to have
  4296  	// the ones with the fees embedded
  4297  	conf.Trades = trades
  4298  	if marginMode == types.MarginModeIsolatedMargin {
  4299  		if err = m.updateIsolatedMarginOnOrder(ctx, posWithTrades, newOrder); err != nil {
  4300  			if m.log.GetLevel() <= logging.DebugLevel {
  4301  				m.log.Debug("Unable to check/add margin for party",
  4302  					logging.Order(*newOrder), logging.Error(err))
  4303  			}
  4304  			return conf, nil, common.ErrMarginCheckFailed
  4305  		}
  4306  	}
  4307  
  4308  	// if the order is not staying in the book, then we remove it
  4309  	// from the potential positions
  4310  	if conf.Order.IsFinished() && conf.Order.Remaining > 0 {
  4311  		_ = m.position.UnregisterOrder(ctx, conf.Order)
  4312  	}
  4313  
  4314  	return conf, orders, nil
  4315  }
  4316  
  4317  func (m *Market) orderAmendInPlace(
  4318  	originalOrder, amendOrder *types.Order,
  4319  ) *types.OrderConfirmation {
  4320  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderAmendInPlace")
  4321  	defer timer.EngineTimeCounterAdd()
  4322  
  4323  	err := m.matching.AmendOrder(originalOrder, amendOrder)
  4324  	if err != nil {
  4325  		// panic here, no good reason for a failure at this point
  4326  		m.log.Panic("Failure after amend order from matching engine (amend-in-place)",
  4327  			logging.OrderWithTag(*amendOrder, "new-order"),
  4328  			logging.OrderWithTag(*originalOrder, "old-order"),
  4329  			logging.Error(err))
  4330  	}
  4331  
  4332  	return &types.OrderConfirmation{
  4333  		Order: amendOrder,
  4334  	}
  4335  }
  4336  
  4337  func (m *Market) orderAmendWhenParked(amendOrder *types.Order) *types.OrderConfirmation {
  4338  	amendOrder.Status = types.OrderStatusParked
  4339  	amendOrder.Price = num.UintZero()
  4340  	amendOrder.OriginalPrice = num.UintZero()
  4341  	m.peggedOrders.AmendParked(amendOrder)
  4342  
  4343  	return &types.OrderConfirmation{
  4344  		Order: amendOrder,
  4345  	}
  4346  }
  4347  
  4348  // submitStopOrders gets a status as parameter.
  4349  // this function is used on trigger but also on submission
  4350  // at expiry, so just filters out with a parameter.
  4351  func (m *Market) submitStopOrders(
  4352  	ctx context.Context,
  4353  	stopOrders []*types.StopOrder,
  4354  	status types.StopOrderStatus,
  4355  	idgen common.IDGenerator,
  4356  ) []*types.OrderConfirmation {
  4357  	confirmations := []*types.OrderConfirmation{}
  4358  	evts := make([]events.Event, 0, len(stopOrders))
  4359  	toDelete := []*types.Order{}
  4360  
  4361  	// might contain both the triggered orders and the expired OCO
  4362  	for _, v := range stopOrders {
  4363  		if v.Status == status {
  4364  			if v.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition {
  4365  				// Update the order size to match that of the party's position
  4366  				var pos int64
  4367  				if position, ok := m.position.GetPositionByPartyID(v.Party); ok {
  4368  					pos = position.Size()
  4369  				}
  4370  
  4371  				// Scale this size if required
  4372  				scaledPos := num.DecimalFromInt64(pos)
  4373  				if v.SizeOverrideValue != nil {
  4374  					scaledPos = scaledPos.Mul(v.SizeOverrideValue.PercentageSize)
  4375  					scaledPos = scaledPos.Round(0)
  4376  				}
  4377  				orderSize := scaledPos.IntPart()
  4378  
  4379  				if orderSize == 0 {
  4380  					// Nothing to do
  4381  					m.log.Error("position is flat so no order required",
  4382  						logging.StopOrderSubmission(v))
  4383  					continue
  4384  				} else if orderSize > 0 {
  4385  					// We are long so need to sell
  4386  					if v.OrderSubmission.Side != types.SideSell {
  4387  						// Don't send an order as we are the wrong direction
  4388  						m.log.Error("triggered order is the wrong side to flatten position",
  4389  							logging.StopOrderSubmission(v))
  4390  						continue
  4391  					}
  4392  					v.OrderSubmission.Size = uint64(orderSize)
  4393  				} else {
  4394  					// We are short so need to buy
  4395  					if v.OrderSubmission.Side != types.SideBuy {
  4396  						// Don't send an order as we are the wrong direction
  4397  						m.log.Error("triggered order is the wrong side to flatten position",
  4398  							logging.StopOrderSubmission(v))
  4399  						continue
  4400  					}
  4401  					v.OrderSubmission.Size = uint64(-orderSize)
  4402  				}
  4403  			}
  4404  
  4405  			conf, err := m.SubmitOrderWithIDGeneratorAndOrderID(
  4406  				ctx, v.OrderSubmission, v.Party, idgen, idgen.NextID(), false,
  4407  			)
  4408  			if err != nil {
  4409  				// not much we can do at that point, let's log the error and move on?
  4410  				m.log.Error("could not submit stop order",
  4411  					logging.StopOrderSubmission(v),
  4412  					logging.Error(err))
  4413  			}
  4414  			if err == nil && conf != nil {
  4415  				v.OrderID = conf.Order.ID
  4416  				confirmations = append(confirmations, conf)
  4417  			}
  4418  		}
  4419  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  4420  	}
  4421  
  4422  	// Remove any referenced orders
  4423  	for _, order := range toDelete {
  4424  		m.CancelOrder(ctx, order.Party, order.ID, order.ID)
  4425  	}
  4426  
  4427  	m.broker.SendBatch(evts)
  4428  
  4429  	return confirmations
  4430  }
  4431  
  4432  // removeExpiredOrders remove all expired orders from the order book
  4433  // and also any pegged orders that are parked.
  4434  func (m *Market) removeExpiredStopOrders(
  4435  	ctx context.Context, timestamp int64, idgen common.IDGenerator,
  4436  ) []*types.OrderConfirmation {
  4437  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredStopOrders")
  4438  	defer timer.EngineTimeCounterAdd()
  4439  
  4440  	toExpire := m.expiringStopOrders.Expire(timestamp)
  4441  	stopOrders := m.stopOrders.RemoveExpired(toExpire)
  4442  
  4443  	//  ensure any OCO orders are also expire
  4444  	toExpireSet := map[string]struct{}{}
  4445  	for _, v := range toExpire {
  4446  		toExpireSet[v] = struct{}{}
  4447  	}
  4448  
  4449  	for _, so := range stopOrders {
  4450  		if _, ok := toExpireSet[so.ID]; !ok {
  4451  			if so.Expiry.Expires() {
  4452  				m.expiringStopOrders.RemoveOrder(so.Expiry.ExpiresAt.UnixNano(), so.ID)
  4453  			}
  4454  		}
  4455  	}
  4456  
  4457  	updatedAt := m.timeService.GetTimeNow()
  4458  
  4459  	if m.as.InAuction() {
  4460  		m.removeExpiredStopOrdersInAuction(ctx, updatedAt, stopOrders)
  4461  		return nil
  4462  	}
  4463  
  4464  	return m.removeExpiredStopOrdersInContinuous(ctx, updatedAt, stopOrders, idgen)
  4465  }
  4466  
  4467  func (m *Market) removeExpiredStopOrdersInAuction(
  4468  	ctx context.Context,
  4469  	updatedAt time.Time,
  4470  	stopOrders []*types.StopOrder,
  4471  ) {
  4472  	evts := []events.Event{}
  4473  	for _, v := range stopOrders {
  4474  		v.UpdatedAt = updatedAt
  4475  		v.Status = types.StopOrderStatusExpired
  4476  		// nothing to do, can send the event now
  4477  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  4478  	}
  4479  
  4480  	m.broker.SendBatch(evts)
  4481  }
  4482  
  4483  func (m *Market) removeExpiredStopOrdersInContinuous(
  4484  	ctx context.Context,
  4485  	updatedAt time.Time,
  4486  	stopOrders []*types.StopOrder,
  4487  	idgen common.IDGenerator,
  4488  ) []*types.OrderConfirmation {
  4489  	evts := []events.Event{}
  4490  	filteredOCO := []*types.StopOrder{}
  4491  	for _, v := range stopOrders {
  4492  		v.UpdatedAt = updatedAt
  4493  		if v.Status == types.StopOrderStatusExpired && v.Expiry.Expires() && *v.Expiry.ExpiryStrategy == types.StopOrderExpiryStrategySubmit {
  4494  			filteredOCO = append(filteredOCO, v)
  4495  			continue
  4496  		}
  4497  		// nothing to do, can send the event now
  4498  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  4499  	}
  4500  
  4501  	m.broker.SendBatch(evts)
  4502  
  4503  	return m.submitStopOrders(ctx, filteredOCO, types.StopOrderStatusExpired, idgen)
  4504  }
  4505  
  4506  // removeExpiredOrders remove all expired orders from the order book
  4507  // and also any pegged orders that are parked.
  4508  func (m *Market) removeExpiredOrders(
  4509  	ctx context.Context, timestamp int64,
  4510  ) []*types.Order {
  4511  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredOrders")
  4512  	defer timer.EngineTimeCounterAdd()
  4513  
  4514  	expired := []*types.Order{}
  4515  	toExp := m.expiringOrders.Expire(timestamp)
  4516  	if len(toExp) == 0 {
  4517  		return expired
  4518  	}
  4519  	ids := make([]string, 0, len(toExp))
  4520  	for _, orderID := range toExp {
  4521  		var order *types.Order
  4522  		// The pegged expiry orders are copies and do not reflect the
  4523  		// current state of the order, therefore we look it up
  4524  		originalOrder, foundOnBook, err := m.getOrderByID(orderID)
  4525  		if err != nil {
  4526  			// nothing to do there.
  4527  			continue
  4528  		}
  4529  		// assign to the order the order from the book
  4530  		// so we get the most recent version from the book
  4531  		// to continue with
  4532  		order = originalOrder
  4533  
  4534  		// if the order was on the book basically
  4535  		// either a pegged + non parked
  4536  		// or a non-pegged order
  4537  		if foundOnBook {
  4538  			pos := m.position.UnregisterOrder(ctx, order)
  4539  			m.matching.DeleteOrder(order)
  4540  			if m.getMarginMode(order.Party) == types.MarginModeIsolatedMargin {
  4541  				err := m.updateIsolatedMarginOnOrder(ctx, pos, order)
  4542  				if err != nil {
  4543  					m.log.Panic("failed to recalculate isolated margin after order cancellation", logging.String("party", order.Party), logging.Order(order))
  4544  				}
  4545  			}
  4546  		}
  4547  
  4548  		// if this was a pegged order
  4549  		// remove from the pegged / parked list
  4550  		if order.PeggedOrder != nil {
  4551  			m.removePeggedOrder(order)
  4552  		}
  4553  
  4554  		// now we add to the list of expired orders
  4555  		// and assign the appropriate status
  4556  		order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
  4557  		order.Status = types.OrderStatusExpired
  4558  		expired = append(expired, order)
  4559  		ids = append(ids, orderID)
  4560  	}
  4561  	if len(ids) > 0 {
  4562  		m.broker.Send(events.NewExpiredOrdersEvent(ctx, m.mkt.ID, ids))
  4563  	}
  4564  
  4565  	// If we have removed an expired order, do we need to reprice any
  4566  	// or maybe notify the liquidity engine
  4567  	if len(expired) > 0 && !m.as.InAuction() {
  4568  		m.checkForReferenceMoves(ctx, expired, false)
  4569  	}
  4570  
  4571  	return expired
  4572  }
  4573  
  4574  func (m *Market) getBestStaticAskPrice() (*num.Uint, error) {
  4575  	return m.matching.GetBestStaticAskPrice()
  4576  }
  4577  
  4578  func (m *Market) getBestStaticAskPriceAndVolume() (*num.Uint, uint64, error) {
  4579  	return m.matching.GetBestStaticAskPriceAndVolume()
  4580  }
  4581  
  4582  func (m *Market) getBestStaticBidPrice() (*num.Uint, error) {
  4583  	return m.matching.GetBestStaticBidPrice()
  4584  }
  4585  
  4586  func (m *Market) getBestStaticBidPriceAndVolume() (*num.Uint, uint64, error) {
  4587  	return m.matching.GetBestStaticBidPriceAndVolume()
  4588  }
  4589  
  4590  func (m *Market) getBestStaticPricesDecimal() (bid, ask num.Decimal, err error) {
  4591  	ask = num.DecimalZero()
  4592  	ubid, err := m.getBestStaticBidPrice()
  4593  	if err != nil {
  4594  		bid = num.DecimalZero()
  4595  		return
  4596  	}
  4597  	bid = ubid.ToDecimal()
  4598  	uask, err := m.getBestStaticAskPrice()
  4599  	if err != nil {
  4600  		ask = num.DecimalZero()
  4601  		return
  4602  	}
  4603  	ask = uask.ToDecimal()
  4604  	return
  4605  }
  4606  
  4607  func (m *Market) getStaticMidPrice(side types.Side) (*num.Uint, error) {
  4608  	bid, err := m.matching.GetBestStaticBidPrice()
  4609  	if err != nil {
  4610  		return num.UintZero(), err
  4611  	}
  4612  	ask, err := m.matching.GetBestStaticAskPrice()
  4613  	if err != nil {
  4614  		return num.UintZero(), err
  4615  	}
  4616  	mid := num.UintZero()
  4617  	one := num.NewUint(1)
  4618  	two := num.Sum(one, one)
  4619  	one, _ = num.UintFromDecimal(one.ToDecimal().Mul(m.priceFactor))
  4620  	if side == types.SideBuy {
  4621  		mid = mid.Div(num.Sum(bid, ask, one), two)
  4622  	} else {
  4623  		mid = mid.Div(num.Sum(bid, ask), two)
  4624  	}
  4625  
  4626  	return mid, nil
  4627  }
  4628  
  4629  // removePeggedOrder looks through the pegged and parked list
  4630  // and removes the matching order if found.
  4631  func (m *Market) removePeggedOrder(order *types.Order) {
  4632  	// remove if order was expiring
  4633  	m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID)
  4634  	// unpark will remove the order from the pegged orders data structure
  4635  	m.peggedOrders.Unpark(order.ID)
  4636  }
  4637  
  4638  // getOrderBy looks for the order in the order book and in the list
  4639  // of pegged orders in the market. Returns the order if found, a bool
  4640  // representing if the order was found on the order book and any error code.
  4641  func (m *Market) getOrderByID(orderID string) (*types.Order, bool, error) {
  4642  	order, err := m.matching.GetOrderByID(orderID)
  4643  	if err == nil {
  4644  		return order, true, nil
  4645  	}
  4646  
  4647  	// The pegged order list contains all the pegged orders in the system
  4648  	// whether they are parked or live. Check this list of a matching order
  4649  	if o := m.peggedOrders.GetParkedByID(orderID); o != nil {
  4650  		return o, false, nil
  4651  	}
  4652  
  4653  	// We couldn't find it
  4654  	return nil, false, common.ErrOrderNotFound
  4655  }
  4656  
  4657  func (m *Market) getTheoreticalTargetStake() *num.Uint {
  4658  	rf := m.risk.GetRiskFactors()
  4659  
  4660  	// Ignoring the error as GetTheoreticalTargetStake handles trades==nil and len(trades)==0
  4661  	trades, _ := m.matching.GetIndicativeTrades()
  4662  
  4663  	return m.tsCalc.GetTheoreticalTargetStake(
  4664  		*rf, m.timeService.GetTimeNow(), m.getReferencePrice(), trades)
  4665  }
  4666  
  4667  func (m *Market) getTargetStake() *num.Uint {
  4668  	return m.tsCalc.GetTargetStake(*m.risk.GetRiskFactors(), m.timeService.GetTimeNow(), m.getCurrentMarkPrice())
  4669  }
  4670  
  4671  func (m *Market) getSuppliedStake() *num.Uint {
  4672  	return m.liquidityEngine.CalculateSuppliedStake()
  4673  }
  4674  
  4675  func (m *Market) tradingTerminated(ctx context.Context, tt bool) {
  4676  	targetState := types.MarketStateSettled
  4677  	if m.mkt.State == types.MarketStatePending {
  4678  		targetState = types.MarketStateCancelled
  4679  	}
  4680  	m.tradingTerminatedWithFinalState(ctx, targetState, nil)
  4681  }
  4682  
  4683  func (m *Market) tradingTerminatedWithFinalState(ctx context.Context, finalState types.MarketState, settlementDataInAsset *num.Uint) {
  4684  	m.mu.Lock()
  4685  	defer m.mu.Unlock()
  4686  	m.terminateMarket(ctx, finalState, settlementDataInAsset)
  4687  }
  4688  
  4689  func (m *Market) terminateMarket(ctx context.Context, finalState types.MarketState, settlementDataInAsset *num.Uint) {
  4690  	// ignore trading termination while the governance proposal hasn't been enacted
  4691  	if m.mkt.State == types.MarketStateProposed {
  4692  		m.log.Debug("market must not terminated before its enactment time", logging.MarketID(m.GetID()))
  4693  		return
  4694  	}
  4695  
  4696  	if finalState != types.MarketStateCancelled {
  4697  		// we're either going to set state to trading terminated
  4698  		// or we'll be performing the final settlement (setting market status to settled)
  4699  		// in both cases, we want to MTM any pending trades
  4700  		// TODO @zohar do we need to keep this check? mark price
  4701  		// can change even if there were no trades
  4702  		if m.settlement.HasTraded() {
  4703  			// we need the ID-gen
  4704  			_, blockHash := vegacontext.TraceIDFromContext(ctx)
  4705  			m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex("finalmtm"+m.GetID()))
  4706  			defer func() {
  4707  				m.idgen = nil
  4708  			}()
  4709  			// we have trades, and the market has been closed. Perform MTM sequence now so the final settlement
  4710  			// works as expected.
  4711  			m.markPriceLock.Lock()
  4712  			m.markPriceCalculator.CalculateMarkPrice(
  4713  				ctx,
  4714  				m.pMonitor,
  4715  				m.as,
  4716  				m.timeService.GetTimeNow().UnixNano(),
  4717  				m.matching,
  4718  				m.mtmDelta,
  4719  				m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin,
  4720  				m.mkt.LinearSlippageFactor,
  4721  				m.risk.GetRiskFactors().Short,
  4722  				m.risk.GetRiskFactors().Long,
  4723  				false,
  4724  				false)
  4725  			m.markPriceLock.Unlock()
  4726  			m.marketActivityTracker.UpdateMarkPrice(m.settlementAsset, m.mkt.ID, m.getCurrentMarkPrice())
  4727  
  4728  			if m.internalCompositePriceCalculator != nil {
  4729  				m.internalCompositePriceCalculator.CalculateMarkPrice(
  4730  					ctx,
  4731  					m.pMonitor,
  4732  					m.as,
  4733  					m.timeService.GetTimeNow().UnixNano(),
  4734  					m.matching,
  4735  					m.internalCompositePriceFrequency,
  4736  					m.tradableInstrument.MarginCalculator.ScalingFactors.InitialMargin,
  4737  					m.mkt.LinearSlippageFactor,
  4738  					m.risk.GetRiskFactors().Short,
  4739  					m.risk.GetRiskFactors().Long,
  4740  					false,
  4741  					false)
  4742  			}
  4743  
  4744  			if m.perp {
  4745  				// if perp and we have an intenal composite price (direct or by mark price), feed it to the perp before the mark to market
  4746  				if m.internalCompositePriceCalculator != nil {
  4747  					if internalCompositePrice := m.getCurrentInternalCompositePrice(); !internalCompositePrice.IsZero() {
  4748  						m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, internalCompositePrice, m.timeService.GetTimeNow().UnixNano())
  4749  					}
  4750  				} else {
  4751  					if internalCompositePrice := m.getCurrentMarkPrice(); !internalCompositePrice.IsZero() {
  4752  						m.tradableInstrument.Instrument.Product.SubmitDataPoint(ctx, internalCompositePrice, m.timeService.GetTimeNow().UnixNano())
  4753  					}
  4754  				}
  4755  			}
  4756  
  4757  			// send market data event with the updated mark price
  4758  			m.broker.Send(events.NewMarketDataEvent(ctx, m.GetMarketData()))
  4759  			m.confirmMTM(ctx, true)
  4760  		}
  4761  		m.mkt.State = types.MarketStateTradingTerminated
  4762  		m.mkt.TradingMode = types.MarketTradingModeNoTrading
  4763  		m.tradableInstrument.Instrument.Product.UnsubscribeTradingTerminated(ctx)
  4764  
  4765  		m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  4766  		var err error
  4767  		if settlementDataInAsset != nil && m.validateSettlementData(settlementDataInAsset) {
  4768  			m.settlementDataWithLock(ctx, finalState, settlementDataInAsset)
  4769  		} else if m.settlementDataInMarket != nil {
  4770  			// because we need to be able to perform the MTM settlement, only update market state now
  4771  			settlementDataInAsset, err = m.tradableInstrument.Instrument.Product.ScaleSettlementDataToDecimalPlaces(m.settlementDataInMarket, m.assetDP)
  4772  			if err != nil {
  4773  				m.log.Error(err.Error())
  4774  				return
  4775  			}
  4776  			if !m.validateSettlementData(settlementDataInAsset) {
  4777  				m.log.Warn("invalid settlement data", logging.MarketID(m.GetID()))
  4778  				m.settlementDataInMarket = nil
  4779  				return
  4780  			}
  4781  			m.settlementDataWithLock(ctx, finalState, settlementDataInAsset)
  4782  		} else {
  4783  			m.log.Debug("no settlement data", logging.MarketID(m.GetID()))
  4784  		}
  4785  		return
  4786  	}
  4787  
  4788  	m.tradableInstrument.Instrument.Product.UnsubscribeTradingTerminated(ctx)
  4789  
  4790  	parties := maps.Keys(m.parties)
  4791  	sort.Strings(parties)
  4792  	for _, party := range parties {
  4793  		_, err := m.CancelAllOrders(ctx, party)
  4794  		if err != nil {
  4795  			m.log.Debug("could not cancel orders for party", logging.PartyID(party), logging.Error(err))
  4796  			panic(err)
  4797  		}
  4798  	}
  4799  	err := m.closeCancelledMarket(ctx)
  4800  	if err != nil {
  4801  		m.log.Debug("could not close market", logging.MarketID(m.GetID()))
  4802  		return
  4803  	}
  4804  }
  4805  
  4806  func (m *Market) scaleOracleData(ctx context.Context, price *num.Numeric, dp int64) *num.Uint {
  4807  	if price == nil {
  4808  		return nil
  4809  	}
  4810  
  4811  	if !price.SupportDecimalPlaces(int64(m.assetDP)) {
  4812  		return nil
  4813  	}
  4814  
  4815  	p, err := price.ScaleTo(dp, int64(m.assetDP))
  4816  	if err != nil {
  4817  		m.log.Error(err.Error())
  4818  		return nil
  4819  	}
  4820  	return p
  4821  }
  4822  
  4823  func (m *Market) settlementData(ctx context.Context, settlementData *num.Numeric) {
  4824  	m.mu.Lock()
  4825  	defer m.mu.Unlock()
  4826  
  4827  	m.settlementDataInMarket = settlementData
  4828  	settlementDataInAsset, err := m.tradableInstrument.Instrument.Product.ScaleSettlementDataToDecimalPlaces(m.settlementDataInMarket, m.assetDP)
  4829  	if err != nil {
  4830  		m.log.Error(err.Error())
  4831  		return
  4832  	}
  4833  
  4834  	// validate the settlement data
  4835  	if !m.validateSettlementData(settlementDataInAsset) {
  4836  		m.log.Warn("settlement data for capped market is invalid", logging.MarketID(m.GetID()))
  4837  		// reset settlement data, it's not valid
  4838  		m.settlementDataInMarket = nil
  4839  		return
  4840  	}
  4841  	m.settlementDataWithLock(ctx, types.MarketStateSettled, settlementDataInAsset)
  4842  }
  4843  
  4844  func (m *Market) settlementDataPerp(ctx context.Context, settlementData *num.Numeric) {
  4845  	// if the market state for a perp is trading terminated then we have come in through goverannce
  4846  	// and will already have the market lock
  4847  	if m.mkt.State != types.MarketStateTradingTerminated {
  4848  		m.mu.Lock()
  4849  		defer m.mu.Unlock()
  4850  	}
  4851  
  4852  	_, blockHash := vegacontext.TraceIDFromContext(ctx)
  4853  	m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex("perpsettlement"+m.GetID()))
  4854  	defer func() {
  4855  		m.idgen = nil
  4856  	}()
  4857  
  4858  	// take all positions, get funding transfers
  4859  	sdi := settlementData.Int()
  4860  	if !settlementData.IsInt() && settlementData.Decimal() != nil {
  4861  		sdi = num.NewInt(settlementData.Decimal().IntPart())
  4862  	}
  4863  	if sdi == nil {
  4864  		return
  4865  	}
  4866  
  4867  	transfers, round := m.settlement.SettleFundingPeriod(ctx, m.position.Positions(), settlementData.Int())
  4868  	if len(transfers) == 0 {
  4869  		m.log.Debug("Failed to get settle positions for funding period")
  4870  		return
  4871  	}
  4872  
  4873  	for _, t := range transfers {
  4874  		m.recordPositionActivity(t.Transfer())
  4875  	}
  4876  	m.broker.Send(events.NewFundingPaymentsEvent(ctx, m.mkt.ID, m.tradableInstrument.Instrument.Product.GetCurrentPeriod(), transfers))
  4877  
  4878  	margins, ledgerMovements, err := m.collateral.PerpsFundingSettlement(ctx, m.GetID(), transfers, m.settlementAsset, round, m.useGeneralAccountForMarginSearch)
  4879  	if err != nil {
  4880  		m.log.Error("Failed to get ledger movements when performing the funding settlement",
  4881  			logging.MarketID(m.GetID()),
  4882  			logging.Error(err))
  4883  		return
  4884  	}
  4885  
  4886  	if len(ledgerMovements) > 0 {
  4887  		m.broker.Send(events.NewLedgerMovements(ctx, ledgerMovements))
  4888  	}
  4889  	// no margin events, no margin stuff to check
  4890  	if len(margins) == 0 {
  4891  		return
  4892  	}
  4893  
  4894  	// split to cross and isolated margins to handle separately
  4895  	crossEvts := make([]events.Margin, 0, len(margins))
  4896  	isolatedEvts := make([]events.Margin, 0, len(margins))
  4897  	for _, evt := range margins {
  4898  		if m.getMarginMode(evt.Party()) == types.MarginModeCrossMargin {
  4899  			crossEvts = append(crossEvts, evt)
  4900  		} else {
  4901  			isolatedEvts = append(isolatedEvts, evt)
  4902  		}
  4903  	}
  4904  
  4905  	// check margin balances
  4906  	increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano())
  4907  	riskUpdates := m.risk.UpdateMarginsOnSettlement(ctx, crossEvts, m.getCurrentMarkPriceForMargin(), increment, m.getAuctionPrice())
  4908  	isolatedMarginPartiesToClose := []events.Risk{}
  4909  	for _, evt := range isolatedEvts {
  4910  		mrgns, err := m.risk.CheckMarginInvariants(ctx, evt, m.getMarketObservable(nil), increment, m.matching.GetOrdersPerParty(evt.Party()), m.getMarginFactor(evt.Party()))
  4911  		if err == risk.ErrInsufficientFundsForMaintenanceMargin {
  4912  			m.log.Debug("party in isolated margin mode has insufficient margin", logging.String("party", evt.Party()))
  4913  			isolatedMarginPartiesToClose = append(isolatedMarginPartiesToClose, mrgns)
  4914  		}
  4915  	}
  4916  
  4917  	// no margin accounts need updating...
  4918  	if len(riskUpdates)+len(isolatedMarginPartiesToClose) == 0 {
  4919  		return
  4920  	}
  4921  	// update margins, close-out any positions that don't have the required margin
  4922  	orderUpdates := m.handleRiskEvts(ctx, riskUpdates, isolatedMarginPartiesToClose)
  4923  	m.checkForReferenceMoves(ctx, orderUpdates, false)
  4924  }
  4925  
  4926  func (m *Market) ValidateSettlementData(data *num.Uint) bool {
  4927  	// convert to asset precision
  4928  	settlement, _ := num.UintFromDecimal(data.ToDecimal().Mul(m.priceFactor))
  4929  	return m.validateSettlementData(settlement)
  4930  }
  4931  
  4932  func (m *Market) validateSettlementData(data *num.Uint) bool {
  4933  	if m.closed {
  4934  		return false
  4935  	}
  4936  	// not capped, accept the data
  4937  	if m.fCap == nil {
  4938  		return true
  4939  	}
  4940  	// data > max
  4941  	if m.capMax.LT(data) {
  4942  		return false
  4943  	}
  4944  	// binary capped market: reject if data is not zero and not == max price.
  4945  	if m.fCap.Binary && !data.IsZero() && !data.EQ(m.capMax) {
  4946  		return false
  4947  	}
  4948  	return true
  4949  }
  4950  
  4951  // NB this must be called with the lock already acquired.
  4952  func (m *Market) settlementDataWithLock(ctx context.Context, finalState types.MarketState, settlementDataInAsset *num.Uint) {
  4953  	if m.closed {
  4954  		return
  4955  	}
  4956  	if m.capMax != nil && m.capMax.LT(settlementDataInAsset) {
  4957  		return
  4958  	}
  4959  	if m.fCap != nil && m.fCap.Binary {
  4960  		// binary settlement is either 0 or max price:
  4961  		if !settlementDataInAsset.IsZero() && !settlementDataInAsset.EQ(m.capMax) {
  4962  			// not zero, not max price -> no final settlement can occur yet
  4963  			return
  4964  		}
  4965  	}
  4966  
  4967  	if m.mkt.State == types.MarketStateTradingTerminated && settlementDataInAsset != nil {
  4968  		err := m.closeMarket(ctx, m.timeService.GetTimeNow(), finalState, settlementDataInAsset)
  4969  		if err != nil {
  4970  			m.log.Error("could not close market", logging.Error(err))
  4971  		}
  4972  		m.closed = m.mkt.State == finalState
  4973  
  4974  		// mark price should be updated here
  4975  		if settlementDataInAsset != nil {
  4976  			m.lastTradedPrice = settlementDataInAsset.Clone()
  4977  			// the settlement price is the final mark price
  4978  			m.markPriceCalculator.OverridePrice(m.lastTradedPrice)
  4979  			if m.internalCompositePriceCalculator != nil {
  4980  				m.internalCompositePriceCalculator.OverridePrice(m.lastTradedPrice)
  4981  			}
  4982  		}
  4983  
  4984  		// send the market data with all updated stuff
  4985  		m.broker.Send(events.NewMarketDataEvent(ctx, m.GetMarketData()))
  4986  		m.broker.Send(events.NewMarketSettled(ctx, m.GetID(), m.timeService.GetTimeNow().UnixNano(), m.lastTradedPrice, m.positionFactor))
  4987  	}
  4988  }
  4989  
  4990  func (m *Market) canTrade() bool {
  4991  	return m.mkt.State == types.MarketStateActive ||
  4992  		m.mkt.State == types.MarketStatePending ||
  4993  		m.mkt.State == types.MarketStateSuspended ||
  4994  		m.mkt.State == types.MarketStateSuspendedViaGovernance
  4995  }
  4996  
  4997  // cleanupOnReject remove all resources created while the
  4998  // market was on PREPARED state.
  4999  // we'll need to remove all accounts related to the market
  5000  // all margin accounts for this market
  5001  // all bond accounts for this market too.
  5002  // at this point no fees would have been collected or anything
  5003  // like this.
  5004  func (m *Market) cleanupOnReject(ctx context.Context) {
  5005  	m.tradableInstrument.Instrument.Unsubscribe(ctx)
  5006  
  5007  	m.markPriceCalculator.Close(ctx)
  5008  	if m.internalCompositePriceCalculator != nil {
  5009  		m.internalCompositePriceCalculator.Close(ctx)
  5010  	}
  5011  
  5012  	// get the list of all parties in this market
  5013  	parties := make([]string, 0, len(m.parties))
  5014  	for k := range m.parties {
  5015  		parties = append(parties, k)
  5016  	}
  5017  
  5018  	m.liquidity.StopAllLiquidityProvision(ctx)
  5019  
  5020  	// cancel all pending orders
  5021  	orders := m.matching.Settled()
  5022  	// stop all parkedPeggedOrders
  5023  	parkedPeggedOrders := m.peggedOrders.Settled()
  5024  
  5025  	evts := make([]events.Event, 0, len(orders)+len(parkedPeggedOrders))
  5026  	for _, o := range append(orders, parkedPeggedOrders...) {
  5027  		evts = append(evts, events.NewOrderEvent(ctx, o))
  5028  	}
  5029  	if len(evts) > 0 {
  5030  		m.broker.SendBatch(evts)
  5031  	}
  5032  
  5033  	// now we do stop orders
  5034  	stopOrders := m.stopOrders.Settled()
  5035  	evts = make([]events.Event, 0, len(stopOrders))
  5036  	for _, o := range stopOrders {
  5037  		evts = append(evts, events.NewStopOrderEvent(ctx, o))
  5038  	}
  5039  	if len(evts) > 0 {
  5040  		m.broker.SendBatch(evts)
  5041  	}
  5042  
  5043  	// release margin balance
  5044  	tresps, err := m.collateral.ClearMarket(ctx, m.GetID(), m.settlementAsset, parties, false)
  5045  	if err != nil {
  5046  		m.log.Panic("unable to cleanup a rejected market",
  5047  			logging.String("market-id", m.GetID()),
  5048  			logging.Error(err))
  5049  		return
  5050  	}
  5051  
  5052  	m.stateVarEngine.UnregisterStateVariable(m.settlementAsset, m.mkt.ID)
  5053  
  5054  	// then send the responses
  5055  	if len(tresps) > 0 {
  5056  		m.broker.Send(events.NewLedgerMovements(ctx, tresps))
  5057  	}
  5058  }
  5059  
  5060  // GetTotalOrderBookLevelCount returns the total number of levels in the order book.
  5061  func (m *Market) GetTotalOrderBookLevelCount() uint64 {
  5062  	return m.matching.GetOrderBookLevelCount()
  5063  }
  5064  
  5065  // GetTotalPeggedOrderCount returns the total number of pegged orders.
  5066  func (m *Market) GetTotalPeggedOrderCount() uint64 {
  5067  	return m.matching.GetPeggedOrdersCount()
  5068  }
  5069  
  5070  // GetTotalStopOrderCount returns the total number of stop orders.
  5071  func (m *Market) GetTotalStopOrderCount() uint64 {
  5072  	return m.stopOrders.GetStopOrderCount()
  5073  }
  5074  
  5075  // GetTotalOpenPositionCount returns the total number of open positions.
  5076  func (m *Market) GetTotalOpenPositionCount() uint64 {
  5077  	return m.position.GetOpenPositionCount()
  5078  }
  5079  
  5080  // getMarketObservable returns current mark price once market is out of opening auction, during opening auction the indicative uncrossing price is returned.
  5081  func (m *Market) getMarketObservable(fallbackPrice *num.Uint) *num.Uint {
  5082  	// this is used for margin calculations, so if there's a max price, return that.
  5083  	if m.capMax != nil && m.fCap.FullyCollateralised {
  5084  		return m.capMax.Clone()
  5085  	}
  5086  	// during opening auction we don't have a last traded price, so we use the indicative price instead
  5087  	if m.as.IsOpeningAuction() {
  5088  		if ip := m.matching.GetIndicativePrice(); !ip.IsZero() {
  5089  			return ip
  5090  		}
  5091  		// we don't have an indicative price yet so we use the supplied price
  5092  		return fallbackPrice
  5093  	}
  5094  	return m.getCurrentMarkPrice()
  5095  }
  5096  
  5097  // Mark price gets returned when market is not in auction, otherwise indicative uncrossing price gets returned.
  5098  func (m *Market) getReferencePrice() *num.Uint {
  5099  	if !m.as.InAuction() {
  5100  		return m.getCurrentMarkPrice()
  5101  	}
  5102  	ip := m.matching.GetIndicativePrice() // can be zero
  5103  	if ip.IsZero() {
  5104  		return m.getCurrentMarkPrice()
  5105  	}
  5106  	return ip
  5107  }
  5108  
  5109  func (m *Market) getCurrentInternalCompositePrice() *num.Uint {
  5110  	if !m.perp || m.internalCompositePriceCalculator == nil {
  5111  		m.log.Panic("trying to get current internal composite price in a market with no intenal composite price configuration or not a perp market")
  5112  	}
  5113  	if m.internalCompositePriceCalculator.GetPrice() == nil {
  5114  		return num.UintZero()
  5115  	}
  5116  	return m.internalCompositePriceCalculator.GetPrice().Clone()
  5117  }
  5118  
  5119  // getCurrentMarkPriceForMargin is the same as getCurrentMarkPrice, but adds a check for capped futures.
  5120  // if this is called on a future with a max price, then the max price will be returned. This is useful for margin checks.
  5121  func (m *Market) getCurrentMarkPriceForMargin() *num.Uint {
  5122  	if m.capMax != nil && m.fCap.FullyCollateralised {
  5123  		return m.capMax.Clone()
  5124  	}
  5125  	return m.getCurrentMarkPrice()
  5126  }
  5127  
  5128  func (m *Market) getCurrentMarkPrice() *num.Uint {
  5129  	m.markPriceLock.RLock()
  5130  	defer m.markPriceLock.RUnlock()
  5131  	if m.markPriceCalculator.GetPrice() == nil {
  5132  		return num.UintZero()
  5133  	}
  5134  	return m.markPriceCalculator.GetPrice().Clone()
  5135  }
  5136  
  5137  func (m *Market) getLastTradedPrice() *num.Uint {
  5138  	if m.lastTradedPrice == nil {
  5139  		return num.UintZero()
  5140  	}
  5141  	return m.lastTradedPrice.Clone()
  5142  }
  5143  
  5144  func (m *Market) GetAssetForProposerBonus() string {
  5145  	return m.settlementAsset
  5146  }
  5147  
  5148  func (m *Market) GetMarketCounters() *types.MarketCounters {
  5149  	return &types.MarketCounters{
  5150  		StopOrderCounter:    m.GetTotalStopOrderCount(),
  5151  		PeggedOrderCounter:  m.GetTotalPeggedOrderCount(),
  5152  		OrderbookLevelCount: m.GetTotalOrderBookLevelCount(),
  5153  		PositionCount:       m.GetTotalOpenPositionCount(),
  5154  	}
  5155  }
  5156  
  5157  func (m *Market) GetRiskFactors() *types.RiskFactor {
  5158  	return m.risk.GetRiskFactors()
  5159  }
  5160  
  5161  func (m *Market) UpdateMarginMode(ctx context.Context, party string, marginMode types.MarginMode, marginFactor num.Decimal) error {
  5162  	if m.fCap != nil && m.fCap.FullyCollateralised {
  5163  		return common.ErrIsolatedMarginFullyCollateralised
  5164  	}
  5165  	if err := m.switchMarginMode(ctx, party, marginMode, marginFactor); err != nil {
  5166  		return err
  5167  	}
  5168  
  5169  	m.emitPartyMarginModeUpdated(ctx, party, marginMode, marginFactor)
  5170  
  5171  	return nil
  5172  }
  5173  
  5174  func (m *Market) getMarginMode(party string) types.MarginMode {
  5175  	marginFactor, ok := m.partyMarginFactor[party]
  5176  	if !ok || marginFactor.IsZero() {
  5177  		return types.MarginModeCrossMargin
  5178  	}
  5179  	return types.MarginModeIsolatedMargin
  5180  }
  5181  
  5182  func (m *Market) useGeneralAccountForMarginSearch(party string) bool {
  5183  	return m.getMarginMode(party) == types.MarginModeCrossMargin
  5184  }
  5185  
  5186  func (m *Market) getMarginFactor(party string) num.Decimal {
  5187  	marginFactor, ok := m.partyMarginFactor[party]
  5188  	if !ok || marginFactor.IsZero() {
  5189  		return num.DecimalZero()
  5190  	}
  5191  	return marginFactor
  5192  }
  5193  
  5194  // switchMarginMode handles a switch between margin modes and/or changes to the margin factor.
  5195  // When switching to isolated margin mode, the following steps will be taken:
  5196  // 1. For any active position, calculate average entry price * abs(position) * margin factor.
  5197  // Calculate the amount of funds which will be added to, or subtracted from, the general account in order to do this.
  5198  // If additional funds must be added which are not available, reject the transaction immediately.
  5199  // 2. For any active orders, calculate the quantity limit price * remaining size * margin factor which needs to be placed in the
  5200  // order margin account. Add this amount to the difference calculated in step 1. If this amount is less than or equal to the
  5201  // amount in the general account, perform the transfers (first move funds into/out of margin account, then move funds into
  5202  // the order margin account). If there are insufficient funds, reject the transaction.
  5203  // 3. Move account to isolated margin mode on this market
  5204  //
  5205  // When switching from isolated margin mode to cross margin mode, the following steps will be taken:
  5206  // 1. Any funds in the order margin account will be moved to the margin account.
  5207  // 2. At this point trading can continue with the account switched to the cross margining account type.
  5208  // If there are excess funds in the margin account they will be freed at the next margin release cycle.
  5209  func (m *Market) switchMarginMode(ctx context.Context, party string, marginMode types.MarginMode, marginFactor num.Decimal) error {
  5210  	defer m.onTxProcessed()
  5211  	if marginMode == m.getMarginMode(party) && marginFactor.Equal(m.getMarginFactor(party)) {
  5212  		return nil
  5213  	}
  5214  	_ = m.addParty(party)
  5215  
  5216  	pos, ok := m.position.GetPositionByPartyID(party)
  5217  	if !ok {
  5218  		pos = positions.NewMarketPosition(party)
  5219  	}
  5220  
  5221  	margins, err := m.collateral.GetPartyMargin(pos, m.settlementAsset, m.GetID())
  5222  	if err == collateral.ErrPartyAccountsMissing {
  5223  		_, err = m.collateral.CreatePartyMarginAccount(ctx, party, m.mkt.ID, m.settlementAsset)
  5224  		if err != nil {
  5225  			return err
  5226  		}
  5227  		margins, err = m.collateral.GetPartyMargin(pos, m.settlementAsset, m.GetID())
  5228  		if err != nil {
  5229  			return err
  5230  		}
  5231  	} else if err != nil {
  5232  		return err
  5233  	}
  5234  
  5235  	marketObservable := m.getMarketObservable(nil)
  5236  	if marketObservable == nil {
  5237  		return fmt.Errorf("no market observable price")
  5238  	}
  5239  	increment := m.tradableInstrument.Instrument.Product.GetMarginIncrease(m.timeService.GetTimeNow().UnixNano())
  5240  	var auctionPrice *num.Uint
  5241  	if m.as.InAuction() {
  5242  		auctionPrice = marketObservable
  5243  		markPrice := m.getCurrentMarkPriceForMargin()
  5244  		if markPrice != nil && marketObservable.LT(markPrice) {
  5245  			auctionPrice = markPrice
  5246  		}
  5247  	}
  5248  	// switching to isolated or changing the margin factor
  5249  	if marginMode == types.MarginModeIsolatedMargin {
  5250  		risk, err := m.risk.SwitchToIsolatedMargin(ctx, margins, marketObservable, increment, m.matching.GetOrdersPerParty(party), marginFactor, auctionPrice)
  5251  		if err != nil {
  5252  			return err
  5253  		}
  5254  		// ensure we have an order margin account set up
  5255  		m.collateral.GetOrCreatePartyOrderMarginAccount(ctx, party, m.mkt.ID, m.settlementAsset)
  5256  		if len(risk) > 0 {
  5257  			for _, r := range risk {
  5258  				err = m.transferMargins(ctx, []events.Risk{r}, nil)
  5259  				if err != nil {
  5260  					return err
  5261  				}
  5262  			}
  5263  		}
  5264  		m.partyMarginFactor[party] = marginFactor
  5265  		// cancel pegged orders
  5266  		ordersAndParkedPegged := append(m.matching.GetOrdersPerParty(party), m.getPartyParkedPeggedOrders(party)...)
  5267  		for _, o := range ordersAndParkedPegged {
  5268  			if o.PeggedOrder != nil {
  5269  				m.cancelOrder(ctx, o.Party, o.ID)
  5270  			}
  5271  		}
  5272  		return nil
  5273  	} else {
  5274  		// switching from isolated margin to cross margin
  5275  		// 1. Any funds in the order margin account will be moved to the margin account.
  5276  		// 2. At this point trading can continue with the account switched to the cross margining account type. If there are excess funds in the margin account they will be freed at the next margin release cycle.
  5277  		risk := m.risk.SwitchFromIsolatedMargin(ctx, margins, marketObservable, increment, m.getAuctionPrice())
  5278  		err = m.transferMargins(ctx, []events.Risk{risk}, nil)
  5279  		if err != nil {
  5280  			return err
  5281  		}
  5282  		delete(m.partyMarginFactor, party)
  5283  		return nil
  5284  	}
  5285  }
  5286  
  5287  func (m *Market) getPartyParkedPeggedOrders(party string) []*types.Order {
  5288  	partyParkedPegged := []*types.Order{}
  5289  	p := m.peggedOrders.Parked()
  5290  	for _, o := range p {
  5291  		if o.Party == party {
  5292  			partyParkedPegged = append(partyParkedPegged, o)
  5293  		}
  5294  	}
  5295  	return partyParkedPegged
  5296  }
  5297  
  5298  func (m *Market) emitPartyMarginModeUpdated(ctx context.Context, party string, mode types.MarginMode, factor num.Decimal) {
  5299  	e := &eventspb.PartyMarginModeUpdated{
  5300  		MarketId:   m.mkt.ID,
  5301  		PartyId:    party,
  5302  		MarginMode: mode,
  5303  		AtEpoch:    m.epoch.Seq,
  5304  	}
  5305  
  5306  	if mode == types.MarginModeIsolatedMargin {
  5307  		e.MarginFactor = ptr.From(factor.String())
  5308  	}
  5309  
  5310  	m.broker.Send(events.NewPartyMarginModeUpdatedEvent(ctx, e))
  5311  }
  5312  
  5313  func (m *Market) checkOrderForSpam(side types.Side, orderPrice *num.Uint, orderSize uint64, peggedOrder *types.PeggedOrder, orderType vegapb.Order_Type, quantumMultiplier num.Decimal) error {
  5314  	rf := num.DecimalOne()
  5315  
  5316  	factor := m.mkt.LinearSlippageFactor
  5317  	if m.risk.IsRiskFactorInitialised() {
  5318  		if side == types.SideBuy {
  5319  			rf = m.risk.GetRiskFactors().Long
  5320  		} else {
  5321  			rf = m.risk.GetRiskFactors().Short
  5322  		}
  5323  	}
  5324  	var price *num.Uint
  5325  	if peggedOrder != nil || orderType == vega.Order_TYPE_MARKET {
  5326  		priceInMarket, _ := num.UintFromDecimal(m.getCurrentMarkPrice().ToDecimal().Div(m.priceFactor))
  5327  		offset := num.UintZero()
  5328  		if peggedOrder != nil {
  5329  			offset = peggedOrder.Offset
  5330  		}
  5331  		if side == types.SideBuy {
  5332  			priceInMarket.AddSum(offset)
  5333  		} else {
  5334  			priceInMarket = priceInMarket.Sub(priceInMarket, offset)
  5335  		}
  5336  		price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
  5337  	} else {
  5338  		price, _ = num.UintFromDecimal(orderPrice.ToDecimal().Mul(m.priceFactor))
  5339  	}
  5340  
  5341  	margins := num.UintZero().Mul(price, num.NewUint(orderSize)).ToDecimal().Div(m.positionFactor)
  5342  
  5343  	assetQuantum, err := m.collateral.GetAssetQuantum(m.settlementAsset)
  5344  	if err != nil {
  5345  		return err
  5346  	}
  5347  	value := margins.Mul(rf.Add(factor))
  5348  	required := quantumMultiplier.Mul(assetQuantum)
  5349  	if value.LessThan(required) {
  5350  		return fmt.Errorf(fmt.Sprintf("order value (%s) is less than minimum maintenance margin for spam (%s)", value.String(), required.String()))
  5351  	}
  5352  	return nil
  5353  }
  5354  
  5355  func (m *Market) checkOrderAmendForSpam(order *types.Order) error {
  5356  	return m.checkOrderForSpam(
  5357  		order.Side,
  5358  		order.Price,
  5359  		order.Size,
  5360  		order.PeggedOrder,
  5361  		order.Type,
  5362  		m.minMaintenanceMarginQuantumMultiplier)
  5363  }
  5364  
  5365  func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmission, party string, quantumMultiplier num.Decimal) error {
  5366  	return m.checkOrderForSpam(
  5367  		orderSubmission.Side,
  5368  		orderSubmission.Price,
  5369  		orderSubmission.Size,
  5370  		orderSubmission.PeggedOrder,
  5371  		orderSubmission.Type,
  5372  		quantumMultiplier)
  5373  }
  5374  
  5375  func (m *Market) GetFillPrice(volume uint64, side types.Side) (*num.Uint, error) {
  5376  	return m.matching.GetFillPrice(volume, side)
  5377  }
  5378  
  5379  func (m *Market) getRebasingOrder(
  5380  	price *num.Uint, // the best bid/ask of the market
  5381  	side types.Side, // side the pool needs to trade as
  5382  	slippage num.Decimal,
  5383  	pool *amm.Pool,
  5384  ) (*types.Order, error) {
  5385  	var volume uint64
  5386  	fairPrice := pool.FairPrice()
  5387  	oneTick, _ := num.UintFromDecimal(m.priceFactor)
  5388  	oneTick = num.Max(num.UintOne(), oneTick)
  5389  
  5390  	var until *num.Uint
  5391  	switch side {
  5392  	case types.SideBuy:
  5393  		until = fairPrice
  5394  		slipped := price.ToDecimal().Mul(num.DecimalOne().Add(slippage))
  5395  		if stopAt, overflow := num.UintFromDecimal(slipped); !overflow {
  5396  			until = num.Min(until, stopAt)
  5397  		}
  5398  	case types.SideSell:
  5399  		until = fairPrice
  5400  		slipped := price.ToDecimal().Mul(num.DecimalOne().Sub(slippage)).Ceil()
  5401  		if stopAt, overflow := num.UintFromDecimal(slipped); !overflow {
  5402  			until = num.Max(until, stopAt)
  5403  		}
  5404  	}
  5405  
  5406  Walk:
  5407  	for {
  5408  		// get the tradable volume necessary to move the AMM's position from fair-price -> price
  5409  		required := pool.TradableVolumeForPrice(types.OtherSide(side), price)
  5410  
  5411  		// AMM is close enough to the target that is has no volume between, so we do not need to rebase
  5412  		if required == 0 {
  5413  			return nil, nil
  5414  		}
  5415  
  5416  		if volume == 0 {
  5417  			volume = required
  5418  		}
  5419  
  5420  		// get the volume available to trade from both the orderbook and other AMM's
  5421  		ammVolume := m.amm.GetVolumeAtPrice(price, side)
  5422  		orderVolume := m.matching.GetVolumeAtPrice(price, types.OtherSide(side))
  5423  
  5424  		// there is enough volume to trade at this level, create the order that the AMM needs to submit
  5425  		if required < orderVolume+ammVolume {
  5426  			originalPrice, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))
  5427  			return &types.Order{
  5428  				ID:            m.idgen.NextID(),
  5429  				MarketID:      m.GetID(),
  5430  				Party:         pool.AMMParty,
  5431  				Side:          side,
  5432  				Price:         price,
  5433  				OriginalPrice: originalPrice,
  5434  				Size:          volume,
  5435  				Remaining:     volume,
  5436  				TimeInForce:   types.OrderTimeInForceIOC,
  5437  				Type:          types.OrderTypeLimit,
  5438  				CreatedAt:     m.timeService.GetTimeNow().UnixNano(),
  5439  				Status:        types.OrderStatusActive,
  5440  				Reference:     "amm-rebase" + pool.AMMParty,
  5441  			}, nil
  5442  		}
  5443  
  5444  		volume = required
  5445  		switch side {
  5446  		case types.SideBuy:
  5447  			// this function will walk the price forwards through price levels until we hit the AMM's fair price or slippage price
  5448  			// i.e 100 -> 101 -> 102
  5449  			price = num.UintZero().Add(price, oneTick)
  5450  			if price.GTE(until) {
  5451  				break Walk
  5452  			}
  5453  		case types.SideSell:
  5454  			// this function will walk the price backwards through price levels until we hit the AMM's fair price or slippage price
  5455  			// i.e 100 -> 99 -> 98
  5456  			price = num.UintZero().Sub(price, oneTick)
  5457  			if price.LTE(until) {
  5458  				break Walk
  5459  			}
  5460  		}
  5461  	}
  5462  
  5463  	return nil, common.ErrAMMCannotRebase
  5464  }
  5465  
  5466  // needsRebase returns whether an AMM at fair-price needs to submit a rebasing order given the current spread on the market.
  5467  func (m *Market) needsRebase(pool *amm.Pool) (bool, types.Side, *num.Uint) {
  5468  	if pool.IsPending() {
  5469  		return false, types.SideUnspecified, nil
  5470  	}
  5471  
  5472  	if m.as.InAuction() {
  5473  		return false, types.SideUnspecified, nil
  5474  	}
  5475  
  5476  	fairPrice := pool.FairPrice()
  5477  
  5478  	ask, err := m.matching.GetBestAskPrice()
  5479  	if err == nil && fairPrice.GT(ask) {
  5480  		return true, types.SideBuy, ask
  5481  	}
  5482  
  5483  	bid, err := m.matching.GetBestBidPrice()
  5484  	if err == nil && fairPrice.LT(bid) {
  5485  		return true, types.SideSell, bid
  5486  	}
  5487  	return false, types.SideUnspecified, nil
  5488  }
  5489  
  5490  func VerifyAMMBounds(params *types.ConcentratedLiquidityParameters, cap *num.Uint, priceFactor num.Decimal) error {
  5491  	var lower, upper, base *num.Uint
  5492  	if params.DataSourceID == nil {
  5493  		base, _ = num.UintFromDecimal(params.Base.ToDecimal().Mul(priceFactor))
  5494  		if cap != nil && base.GTE(cap) {
  5495  			return common.ErrAMMBoundsOutsidePriceCap
  5496  		}
  5497  	}
  5498  
  5499  	if params.LowerBound != nil {
  5500  		lower, _ = num.UintFromDecimal(params.LowerBound.ToDecimal().Mul(priceFactor))
  5501  		if base != nil && lower.GTE(base) {
  5502  			return fmt.Errorf("base (%s) as factored by market and asset decimals must be greater than lower bound (%s)", base.String(), lower.String())
  5503  		}
  5504  
  5505  		if cap != nil && lower.GTE(cap) {
  5506  			return common.ErrAMMBoundsOutsidePriceCap
  5507  		}
  5508  	}
  5509  
  5510  	if params.UpperBound != nil {
  5511  		upper, _ = num.UintFromDecimal(params.UpperBound.ToDecimal().Mul(priceFactor))
  5512  		if base != nil && base.GTE(upper) {
  5513  			return fmt.Errorf("upper bound (%s) as factored by market and asset decimals must be greater than base (%s)", upper.String(), base.String())
  5514  		}
  5515  
  5516  		if cap != nil && upper.GTE(cap) {
  5517  			return common.ErrAMMBoundsOutsidePriceCap
  5518  		}
  5519  	}
  5520  
  5521  	if lower != nil && upper != nil && lower.GTE(upper) {
  5522  		return fmt.Errorf("upper bound (%s) as factored by market and asset decimals must be greater than lower (%s)", upper.String(), lower.String())
  5523  	}
  5524  
  5525  	return nil
  5526  }
  5527  
  5528  // productDataSourcePropagation is a call back for whenever the product receives a data-point used to drive any settlement data
  5529  // we want to hijack it to use as the base point for AMM's.
  5530  func (m *Market) productDataSourcePropagation(ctx context.Context, assetPrice *num.Uint) {
  5531  	if perp := m.mkt.TradableInstrument.Instrument.GetPerps(); perp != nil {
  5532  		m.dataSourcePropagation(ctx, perp.DataSourceSpecForSettlementData.ID, assetPrice)
  5533  	}
  5534  }
  5535  
  5536  // dataSourcePropagation is a call back for whenever a data-point used to drive any settlement data
  5537  // we want to hijack it to use as the base point for AMM's.
  5538  func (m *Market) dataSourcePropagation(ctx context.Context, dataSourceID string, assetPrice *num.Uint) {
  5539  	_, blockHash := vgcontext.TraceIDFromContext(ctx)
  5540  
  5541  	// we want to update any AMM's which have this id as their base price source
  5542  	pools := m.amm.GetDataSourcedAMMs(dataSourceID)
  5543  
  5544  	// now one buy one lets update, treating it like an amend
  5545  	for i, p := range pools {
  5546  		params := p.Parameters.Clone()
  5547  
  5548  		// scale to market DP
  5549  		params.Base, _ = num.UintFromDecimal(assetPrice.ToDecimal().Div(m.priceFactor))
  5550  
  5551  		// if base price hasn't moved enough we won't update
  5552  		if !p.IsPending() && p.MinimumPriceChangeTrigger.IsPositive() {
  5553  			d := p.Parameters.Base.ToDecimal().Div(params.Base.ToDecimal()).Sub(num.DecimalOne()).Abs()
  5554  			if d.LessThan(p.MinimumPriceChangeTrigger) {
  5555  				m.log.Info("not rebasing pool, price change too small",
  5556  					logging.String("market-id", m.mkt.GetID()),
  5557  					logging.String("amm-party-id", p.AMMParty),
  5558  					logging.String("base", p.Parameters.Base.String()),
  5559  					logging.String("new base", params.Base.String()),
  5560  				)
  5561  				continue
  5562  			}
  5563  		}
  5564  
  5565  		params.DataSourceID = nil
  5566  		if err := VerifyAMMBounds(params, m.capMax, m.priceFactor); err != nil {
  5567  			m.log.Error("unable to update AMM base price from data source", logging.Error(err), logging.String("amm-party", p.AMMParty))
  5568  			continue
  5569  		}
  5570  
  5571  		params.DataSourceID = p.Parameters.DataSourceID
  5572  
  5573  		// lets treat the update as an amend
  5574  		amend := &types.AmendAMM{
  5575  			AMMBaseCommand: types.AMMBaseCommand{
  5576  				Party:                     p.Owner(),
  5577  				MarketID:                  m.mkt.GetID(),
  5578  				SlippageTolerance:         p.SlippageTolerance,
  5579  				ProposedFee:               p.ProposedFee,
  5580  				MinimumPriceChangeTrigger: p.MinimumPriceChangeTrigger,
  5581  			},
  5582  			CommitmentAmount: nil,
  5583  			Parameters:       params,
  5584  		}
  5585  		deterministicID := blockHash + crypto.HashStrToHex("amm-oracle-rebase"+m.mkt.ID+strconv.FormatInt(int64(i), 10))
  5586  		err := m.AmendAMM(ctx, amend, deterministicID)
  5587  		if err != nil {
  5588  			m.log.Error("unable to update AMM base price from data source", logging.Error(err), logging.String("amm-party", p.AMMParty))
  5589  		}
  5590  	}
  5591  }
  5592  
  5593  func (m *Market) SubmitAMM(ctx context.Context, submit *types.SubmitAMM, deterministicID string) error {
  5594  	if !m.canTrade() {
  5595  		return common.ErrTradingNotAllowed
  5596  	}
  5597  
  5598  	m.idgen = idgeneration.New(deterministicID)
  5599  	defer func() { m.idgen = nil }()
  5600  
  5601  	// create the AMM curves but do not confirm it with the engine
  5602  	var order *types.Order
  5603  	if err := VerifyAMMBounds(submit.Parameters, m.capMax, m.priceFactor); err != nil {
  5604  		return err
  5605  	}
  5606  
  5607  	pool, err := m.amm.Create(ctx, submit, m.idgen.NextID(), m.risk.GetRiskFactors(), m.risk.GetScalingFactors(), m.risk.GetSlippage())
  5608  	if err != nil {
  5609  		return err
  5610  	}
  5611  
  5612  	// create a rebasing order if the AMM needs it i.e its base if not within best-bid/best-ask
  5613  	if ok, side, quote := m.needsRebase(pool); ok {
  5614  		order, err = m.getRebasingOrder(quote, side, submit.SlippageTolerance, pool)
  5615  		if err != nil {
  5616  			m.broker.Send(
  5617  				events.NewAMMPoolEvent(
  5618  					ctx, pool.Owner(), m.GetID(), pool.AMMParty, pool.ID,
  5619  					pool.CommitmentAmount(), pool.Parameters,
  5620  					types.AMMPoolStatusRejected, types.AMMStatusReasonCannotRebase,
  5621  					pool.ProposedFee, nil, nil, num.DecimalZero(),
  5622  				),
  5623  			)
  5624  			return err
  5625  		}
  5626  	}
  5627  
  5628  	_, err = m.amm.UpdateSubAccountBalance(ctx, submit.Party, pool.AMMParty, submit.CommitmentAmount)
  5629  	if err != nil {
  5630  		m.broker.Send(
  5631  			events.NewAMMPoolEvent(
  5632  				ctx, submit.Party, m.GetID(), pool.AMMParty, pool.ID,
  5633  				submit.CommitmentAmount, submit.Parameters,
  5634  				types.AMMPoolStatusRejected, types.AMMStatusReasonCannotFillCommitment,
  5635  				pool.ProposedFee, nil, nil, num.DecimalZero(),
  5636  			),
  5637  		)
  5638  		return err
  5639  	}
  5640  
  5641  	// if a rebase is not necessary we're done, just confirm with the amm-engine
  5642  	if order == nil {
  5643  		m.amm.Confirm(ctx, pool)
  5644  		m.matching.UpdateAMM(pool.AMMParty)
  5645  		m.checkForReferenceMoves(ctx, nil, false)
  5646  		return nil
  5647  	}
  5648  
  5649  	if conf, _, err := m.submitValidatedOrder(ctx, order); err != nil || len(conf.Trades) == 0 {
  5650  		m.log.Error("failed to submit rebasing order",
  5651  			logging.Order(order),
  5652  			logging.Error(err),
  5653  		)
  5654  		ledgerMovements, _, _ := m.collateral.SubAccountRelease(ctx, submit.Party, pool.AMMParty, m.GetSettlementAsset(), m.GetID(), nil)
  5655  		m.broker.Send(events.NewLedgerMovements(ctx, ledgerMovements))
  5656  		m.broker.Send(
  5657  			events.NewAMMPoolEvent(
  5658  				ctx, submit.Party, m.GetID(), pool.AMMParty, pool.ID,
  5659  				submit.CommitmentAmount, submit.Parameters,
  5660  				types.AMMPoolStatusRejected, types.AMMStatusReasonCannotRebase,
  5661  				pool.ProposedFee, nil, nil, num.DecimalZero(),
  5662  			),
  5663  		)
  5664  		return err
  5665  	}
  5666  
  5667  	// rebase successful so confirm the pool with the engine
  5668  	m.amm.Confirm(ctx, pool)
  5669  	// now tell the matching engine something new has appeared incase it needs to update its auction IPV cache
  5670  	m.matching.UpdateAMM(pool.AMMParty)
  5671  	m.checkForReferenceMoves(ctx, nil, false)
  5672  	return nil
  5673  }
  5674  
  5675  func (m *Market) AmendAMM(ctx context.Context, amend *types.AmendAMM, deterministicID string) error {
  5676  	if !m.canTrade() {
  5677  		return common.ErrTradingNotAllowed
  5678  	}
  5679  
  5680  	m.idgen = idgeneration.New(deterministicID)
  5681  	defer func() { m.idgen = nil }()
  5682  
  5683  	if amend.Parameters != nil {
  5684  		if err := VerifyAMMBounds(amend.Parameters, m.capMax, m.priceFactor); err != nil {
  5685  			return err
  5686  		}
  5687  	}
  5688  
  5689  	// get an amended AMM and the existing AMM
  5690  	pool, existing, err := m.amm.Amend(ctx, amend, m.risk.GetRiskFactors(), m.risk.GetScalingFactors(), m.risk.GetSlippage())
  5691  	if err != nil {
  5692  		return err
  5693  	}
  5694  	// if we failed to rebase the amended pool be sure to reinstante the old one
  5695  	defer func() {
  5696  		if err != nil {
  5697  			m.amm.Confirm(ctx, existing)
  5698  		}
  5699  	}()
  5700  
  5701  	var order *types.Order
  5702  	if ok, side, quote := m.needsRebase(pool); ok {
  5703  		order, err = m.getRebasingOrder(quote, side, amend.SlippageTolerance, pool)
  5704  		if err != nil {
  5705  			return err
  5706  		}
  5707  	}
  5708  
  5709  	// update commitment ready for rebasing
  5710  	var prevCommitment *num.Uint
  5711  	if amend.CommitmentAmount != nil {
  5712  		prevCommitment, err = m.amm.UpdateSubAccountBalance(ctx, pool.Owner(), pool.AMMParty, amend.CommitmentAmount)
  5713  		if err != nil {
  5714  			return err
  5715  		}
  5716  	}
  5717  
  5718  	if order == nil {
  5719  		m.amm.Confirm(ctx, pool)
  5720  		m.matching.UpdateAMM(pool.AMMParty)
  5721  		m.checkForReferenceMoves(ctx, nil, false)
  5722  		return nil
  5723  	}
  5724  
  5725  	conf, _, err := m.submitValidatedOrder(ctx, order)
  5726  	if err != nil || len(conf.Trades) == 0 {
  5727  		m.log.Error("failed to submit rebasing order",
  5728  			logging.Order(order),
  5729  			logging.Error(err),
  5730  		)
  5731  		if amend.CommitmentAmount != nil {
  5732  			if _, err := m.amm.UpdateSubAccountBalance(ctx, pool.Owner(), pool.AMMParty, prevCommitment); err != nil {
  5733  				m.log.Panic("unable to restore AMM balances after failed amend", logging.Error(err))
  5734  			}
  5735  		}
  5736  		err = common.ErrAMMCannotRebase // set it to err so that the defer runs
  5737  		return err
  5738  	}
  5739  
  5740  	m.amm.Confirm(ctx, pool)
  5741  	m.matching.UpdateAMM(pool.AMMParty)
  5742  	m.checkForReferenceMoves(ctx, nil, false)
  5743  	return nil
  5744  }
  5745  
  5746  func (m *Market) CancelAMM(ctx context.Context, cancel *types.CancelAMM, deterministicID string) error {
  5747  	if !m.canTrade() {
  5748  		return common.ErrTradingNotAllowed
  5749  	}
  5750  
  5751  	ammParty, err := m.amm.GetAMMParty(cancel.Party)
  5752  	if err != nil {
  5753  		return err
  5754  	}
  5755  
  5756  	closeout, err := m.amm.CancelAMM(ctx, cancel)
  5757  	if err != nil {
  5758  		return err
  5759  	}
  5760  
  5761  	// tell matching incase it needs to remove the AMM's contribution to the IPV cache
  5762  	m.matching.UpdateAMM(ammParty)
  5763  
  5764  	// rejig any pegged orders that might need re-pricing now an AMM is not longer there, or is no longer quoting one side
  5765  	m.checkForReferenceMoves(ctx, nil, false)
  5766  
  5767  	if closeout == nil {
  5768  		return nil
  5769  	}
  5770  
  5771  	// pool is closed but now its position needs to be closed out
  5772  	m.idgen = idgeneration.New(deterministicID)
  5773  	defer func() { m.idgen = nil }()
  5774  	m.resolveClosedOutParties(ctx, []events.Margin{closeout})
  5775  	return nil
  5776  }