code.vegaprotocol.io/vega@v0.79.0/core/execution/spot/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 spot
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"sort"
    23  	"sync"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/assets"
    27  	"code.vegaprotocol.io/vega/core/events"
    28  	"code.vegaprotocol.io/vega/core/execution/common"
    29  	"code.vegaprotocol.io/vega/core/execution/stoporders"
    30  	"code.vegaprotocol.io/vega/core/fee"
    31  	"code.vegaprotocol.io/vega/core/idgeneration"
    32  	liquiditytarget "code.vegaprotocol.io/vega/core/liquidity/target/spot"
    33  	"code.vegaprotocol.io/vega/core/liquidity/v2"
    34  	"code.vegaprotocol.io/vega/core/matching"
    35  	"code.vegaprotocol.io/vega/core/metrics"
    36  	"code.vegaprotocol.io/vega/core/monitor/price"
    37  	"code.vegaprotocol.io/vega/core/risk"
    38  	"code.vegaprotocol.io/vega/core/types"
    39  	"code.vegaprotocol.io/vega/core/types/statevar"
    40  	vegacontext "code.vegaprotocol.io/vega/libs/context"
    41  	"code.vegaprotocol.io/vega/libs/crypto"
    42  	"code.vegaprotocol.io/vega/libs/num"
    43  	"code.vegaprotocol.io/vega/libs/ptr"
    44  	"code.vegaprotocol.io/vega/logging"
    45  	"code.vegaprotocol.io/vega/protos/vega"
    46  )
    47  
    48  const (
    49  	BaseAssetIndex  = 0
    50  	QuoteAssetIndex = 1
    51  )
    52  
    53  type TargetStakeCalculator interface {
    54  	types.StateProvider
    55  	RecordTotalStake(oi uint64, now time.Time) error
    56  	GetTargetStake(now time.Time) *num.Uint
    57  	UpdateScalingFactor(sFactor num.Decimal) error
    58  	UpdateTimeWindow(tWindow time.Duration)
    59  	StopSnapshots()
    60  	UpdateParameters(types.TargetStakeParameters)
    61  }
    62  
    63  // Market represents an instance of a market in vega and is in charge of calling the engines in order to process all transactions.
    64  type Market struct {
    65  	log   *logging.Logger
    66  	idgen common.IDGenerator
    67  
    68  	mkt *types.Market
    69  
    70  	closingAt   time.Time
    71  	timeService common.TimeService
    72  
    73  	mu            sync.RWMutex
    74  	markPriceLock sync.RWMutex
    75  
    76  	lastTradedPrice *num.Uint
    77  	markPrice       *num.Uint
    78  	priceFactor     num.Decimal
    79  	quoteAssetDP    uint32
    80  
    81  	// own engines
    82  	matching                      *matching.CachedOrderBook
    83  	fee                           *fee.Engine
    84  	referralDiscountRewardService fee.ReferralDiscountRewardService
    85  	volumeDiscountService         fee.VolumeDiscountService
    86  	volumeRebateService           fee.VolumeRebateService
    87  	liquidity                     *common.MarketLiquidity
    88  	liquidityEngine               common.LiquidityEngine
    89  
    90  	// deps engines
    91  	collateral common.Collateral
    92  	banking    common.Banking
    93  
    94  	broker               common.Broker
    95  	closed               bool
    96  	finalFeesDistributed bool
    97  
    98  	parties map[string]struct{}
    99  
   100  	pMonitor common.PriceMonitor
   101  
   102  	tsCalc TargetStakeCalculator
   103  
   104  	as common.AuctionState
   105  
   106  	peggedOrders   *common.PeggedOrders
   107  	expiringOrders *common.ExpiringOrders
   108  
   109  	// Store the previous price values so we can see what has changed
   110  	lastBestBidPrice *num.Uint
   111  	lastBestAskPrice *num.Uint
   112  	lastMidBuyPrice  *num.Uint
   113  	lastMidSellPrice *num.Uint
   114  
   115  	lastMarketValueProxy        num.Decimal
   116  	marketValueWindowLength     time.Duration
   117  	minHoldingQuantumMultiplier num.Decimal
   118  
   119  	// Liquidity Fee
   120  	feeSplitter                *common.FeeSplitter
   121  	lastEquityShareDistributed time.Time
   122  	equityShares               *common.EquityShares
   123  	minLPStakeQuantumMultiple  num.Decimal
   124  
   125  	stateVarEngine        common.StateVarEngine
   126  	marketActivityTracker *common.MarketActivityTracker
   127  	baseFactor            num.Decimal // 10^(baseDP-pdp)
   128  	positionFactor        num.Decimal // 10^pdp
   129  
   130  	orderHoldingTracker *HoldingAccountTracker
   131  
   132  	nextMTM    time.Time
   133  	mtmDelta   time.Duration
   134  	hasTraded  bool
   135  	baseAsset  string
   136  	quoteAsset string
   137  
   138  	maxStopOrdersPerParties *num.Uint
   139  	stopOrders              *stoporders.Pool
   140  	expiringStopOrders      *common.ExpiringOrders
   141  
   142  	minDuration time.Duration
   143  	epoch       types.Epoch
   144  
   145  	pap            *ProtocolAutomatedPurchase
   146  	allowedSellers map[string]struct{}
   147  }
   148  
   149  // NewMarket creates a new market using the market framework configuration and creates underlying engines.
   150  func NewMarket(
   151  	log *logging.Logger,
   152  	matchingConfig matching.Config,
   153  	feeConfig fee.Config,
   154  	liquidityConfig liquidity.Config,
   155  	collateralEngine common.Collateral,
   156  	mkt *types.Market,
   157  	timeService common.TimeService,
   158  	broker common.Broker,
   159  	as common.AuctionState,
   160  	stateVarEngine common.StateVarEngine,
   161  	marketActivityTracker *common.MarketActivityTracker,
   162  	baseAssetDetails *assets.Asset,
   163  	quoteAssetDetails *assets.Asset,
   164  	peggedOrderNotify func(int64),
   165  	referralDiscountRewardService fee.ReferralDiscountRewardService,
   166  	volumeDiscountService fee.VolumeDiscountService,
   167  	volumeRebateService fee.VolumeRebateService,
   168  	banking common.Banking,
   169  ) (*Market, error) {
   170  	if len(mkt.ID) == 0 {
   171  		return nil, common.ErrEmptyMarketID
   172  	}
   173  
   174  	positionFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(mkt.PositionDecimalPlaces))
   175  	priceFactor := num.DecimalOne()
   176  	if exp := int(quoteAssetDetails.DecimalPlaces()) - int(mkt.DecimalPlaces); exp != 0 {
   177  		priceFactor = num.DecimalFromInt64(10).Pow(num.DecimalFromInt64(int64(exp)))
   178  	}
   179  	baseFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(int64(baseAssetDetails.DecimalPlaces()) - mkt.PositionDecimalPlaces))
   180  	book := matching.NewCachedOrderBook(log, matchingConfig, mkt.ID, as.InAuction(), peggedOrderNotify)
   181  	assets, err := mkt.GetAssets()
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	if len(assets) != 2 {
   187  		return nil, fmt.Errorf("expecting base asset and quote asset for spot market")
   188  	}
   189  
   190  	baseAsset := assets[BaseAssetIndex]
   191  	quoteAsset := assets[QuoteAssetIndex]
   192  	feeEngine, err := fee.New(log, feeConfig, *mkt.Fees, quoteAsset, positionFactor)
   193  	if err != nil {
   194  		return nil, fmt.Errorf("unable to instantiate fee engine: %w", err)
   195  	}
   196  
   197  	tsCalc := liquiditytarget.NewSnapshotEngine(*mkt.LiquidityMonitoringParameters.TargetStakeParameters, mkt.ID, positionFactor)
   198  	riskModel, err := risk.NewModel(mkt.TradableInstrument.RiskModel, quoteAsset)
   199  	if err != nil {
   200  		return nil, fmt.Errorf("unable to instantiate risk model: %w", err)
   201  	}
   202  	pMonitor, err := price.NewMonitor(quoteAsset, mkt.ID, riskModel, as, mkt.PriceMonitoringSettings, stateVarEngine, log)
   203  	if err != nil {
   204  		return nil, fmt.Errorf("unable to instantiate price monitoring engine: %w", err)
   205  	}
   206  
   207  	now := timeService.GetTimeNow()
   208  
   209  	// The market is initially created in a proposed state
   210  	mkt.State = types.MarketStateProposed
   211  	mkt.TradingMode = types.MarketTradingModeNoTrading
   212  
   213  	// Populate the market timestamps
   214  	ts := &types.MarketTimestamps{
   215  		Proposed: now.UnixNano(),
   216  		Pending:  now.UnixNano(),
   217  	}
   218  
   219  	if mkt.OpeningAuction != nil {
   220  		ts.Open = now.Add(time.Duration(mkt.OpeningAuction.Duration)).UnixNano()
   221  	} else {
   222  		ts.Open = now.UnixNano()
   223  	}
   224  
   225  	mkt.MarketTimestamps = ts
   226  	liquidity := liquidity.NewSnapshotEngine(liquidityConfig, log, timeService, broker, riskModel, pMonitor, book, as, quoteAsset, mkt.ID, stateVarEngine, positionFactor, mkt.LiquiditySLAParams)
   227  	els := common.NewEquityShares(num.DecimalZero())
   228  	// @TODO pass in AMM
   229  	marketLiquidity := common.NewMarketLiquidity(log, liquidity, collateralEngine, broker, book, els, marketActivityTracker, feeEngine, common.SpotMarketType, mkt.ID, quoteAsset, priceFactor, mkt.LiquiditySLAParams.PriceRange, nil)
   230  
   231  	allowedSellers := map[string]struct{}{}
   232  	for _, v := range mkt.AllowedSellers {
   233  		allowedSellers[v] = struct{}{}
   234  	}
   235  
   236  	market := &Market{
   237  		log:                           log,
   238  		idgen:                         nil,
   239  		mkt:                           mkt,
   240  		matching:                      book,
   241  		collateral:                    collateralEngine,
   242  		timeService:                   timeService,
   243  		broker:                        broker,
   244  		fee:                           feeEngine,
   245  		referralDiscountRewardService: referralDiscountRewardService,
   246  		volumeDiscountService:         volumeDiscountService,
   247  		volumeRebateService:           volumeRebateService,
   248  		parties:                       map[string]struct{}{},
   249  		as:                            as,
   250  		pMonitor:                      pMonitor,
   251  		liquidity:                     marketLiquidity,
   252  		liquidityEngine:               liquidity,
   253  		tsCalc:                        tsCalc,
   254  		peggedOrders:                  common.NewPeggedOrders(log, timeService),
   255  		expiringOrders:                common.NewExpiringOrders(),
   256  		feeSplitter:                   common.NewFeeSplitter(),
   257  		equityShares:                  els,
   258  		lastBestAskPrice:              num.UintZero(),
   259  		lastMidSellPrice:              num.UintZero(),
   260  		lastMidBuyPrice:               num.UintZero(),
   261  		lastBestBidPrice:              num.UintZero(),
   262  		stateVarEngine:                stateVarEngine,
   263  		marketActivityTracker:         marketActivityTracker,
   264  		priceFactor:                   priceFactor,
   265  		baseFactor:                    baseFactor,
   266  		minLPStakeQuantumMultiple:     num.MustDecimalFromString("1"),
   267  		positionFactor:                positionFactor,
   268  		baseAsset:                     baseAsset,
   269  		quoteAsset:                    quoteAsset,
   270  		orderHoldingTracker:           NewHoldingAccountTracker(mkt.ID, log, collateralEngine),
   271  		nextMTM:                       time.Time{}, // default to zero time
   272  		maxStopOrdersPerParties:       num.UintZero(),
   273  		stopOrders:                    stoporders.New(log),
   274  		expiringStopOrders:            common.NewExpiringOrders(),
   275  		banking:                       banking,
   276  		allowedSellers:                allowedSellers,
   277  	}
   278  	liquidity.SetGetStaticPricesFunc(market.getBestStaticPricesDecimal)
   279  
   280  	market.quoteAssetDP = uint32(quoteAssetDetails.DecimalPlaces())
   281  	return market, nil
   282  }
   283  
   284  func (m *Market) GetAssets() []string {
   285  	return []string{m.baseAsset, m.quoteAsset}
   286  }
   287  
   288  func (m *Market) IsOpeningAuction() bool {
   289  	return m.as.IsOpeningAuction()
   290  }
   291  
   292  func (m *Market) getParties() []string {
   293  	parties := make([]string, 0, len(m.parties))
   294  	for k := range m.parties {
   295  		parties = append(parties, k)
   296  	}
   297  	sort.Strings(parties)
   298  	return parties
   299  }
   300  
   301  func (m *Market) GetPartiesStats() *types.MarketStats {
   302  	return &types.MarketStats{}
   303  }
   304  
   305  func (m *Market) Update(ctx context.Context, config *types.Market) error {
   306  	tickSizeChanged := config.TickSize.NEQ(m.mkt.TickSize)
   307  	config.TradingMode = m.mkt.TradingMode
   308  	config.State = m.mkt.State
   309  	config.MarketTimestamps = m.mkt.MarketTimestamps
   310  	m.mkt = config
   311  
   312  	m.tsCalc.UpdateParameters(*config.LiquidityMonitoringParameters.TargetStakeParameters)
   313  	riskModel, err := risk.NewModel(config.TradableInstrument.RiskModel, m.quoteAsset)
   314  	if err != nil {
   315  		return err
   316  	}
   317  	m.pMonitor.UpdateSettings(riskModel, m.mkt.PriceMonitoringSettings, m.as)
   318  	m.liquidity.UpdateMarketConfig(riskModel, m.pMonitor)
   319  	m.updateLiquidityFee(ctx)
   320  
   321  	clear(m.allowedSellers)
   322  	for _, v := range config.AllowedSellers {
   323  		m.allowedSellers[v] = struct{}{}
   324  	}
   325  
   326  	if tickSizeChanged {
   327  		tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor))
   328  		peggedOrders := m.matching.GetActivePeggedOrderIDs()
   329  		peggedOrders = append(peggedOrders, m.peggedOrders.GetParkedIDs()...)
   330  		for _, po := range peggedOrders {
   331  			order, err := m.matching.GetOrderByID(po)
   332  			if err != nil {
   333  				order = m.peggedOrders.GetParkedByID(po)
   334  				if order == nil {
   335  					continue
   336  				}
   337  			}
   338  			offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor))
   339  			if !num.UintZero().Mod(order.PeggedOrder.Offset, m.mkt.TickSize).IsZero() ||
   340  				(order.PeggedOrder.Reference == types.PeggedReferenceMid && offsetInAsset.IsZero() && tickSizeInAsset.IsZero()) {
   341  				m.cancelOrder(ctx, order.Party, order.ID)
   342  			}
   343  		}
   344  	}
   345  
   346  	// update immediately during opening auction
   347  	if m.as.IsOpeningAuction() {
   348  		m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams)
   349  	}
   350  
   351  	return nil
   352  }
   353  
   354  func (m *Market) GetEquityShares() *common.EquityShares {
   355  	return m.equityShares
   356  }
   357  
   358  func (m *Market) GetEquitySharesForParty(partyID string) num.Decimal {
   359  	primary := m.equityShares.SharesFromParty(partyID)
   360  	// AMM for spot has not been implemented yet
   361  	// if sub, err := m.amm.GetAMMParty(partyID); err == nil {
   362  	// return primary.Add(m.equityShares.SharesFromParty(sub))
   363  	// }
   364  	return primary
   365  }
   366  
   367  func (m *Market) SetNextMTM(tm time.Time) {
   368  	m.nextMTM = tm
   369  }
   370  
   371  func (m *Market) GetNextMTM() time.Time {
   372  	return m.nextMTM
   373  }
   374  
   375  func (m *Market) midPrice() *num.Uint {
   376  	bestBidPrice, _, _ := m.matching.BestBidPriceAndVolume()
   377  	bestOfferPrice, _, _ := m.matching.BestOfferPriceAndVolume()
   378  	two := num.NewUint(2)
   379  	midPrice := num.UintZero()
   380  	if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() {
   381  		midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two)
   382  	}
   383  	return midPrice
   384  }
   385  
   386  func (m *Market) IntoType() types.Market {
   387  	return *m.mkt.DeepClone()
   388  }
   389  
   390  func (m *Market) Hash() []byte {
   391  	mID := logging.String("market-id", m.GetID())
   392  	matchingHash := m.matching.Hash()
   393  	m.log.Debug("orderbook state hash", logging.Hash(matchingHash), mID)
   394  	return matchingHash
   395  }
   396  
   397  func (m *Market) GetMarketState() types.MarketState {
   398  	return m.mkt.State
   399  }
   400  
   401  func (m *Market) priceToMarketPrecision(price *num.Uint) *num.Uint {
   402  	p, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))
   403  	return p
   404  }
   405  
   406  func (m *Market) GetMarketData() types.MarketData {
   407  	bestBidPrice, bestBidVolume, _ := m.matching.BestBidPriceAndVolume()
   408  	bestOfferPrice, bestOfferVolume, _ := m.matching.BestOfferPriceAndVolume()
   409  	bestStaticBidPrice, bestStaticBidVolume, _ := m.getBestStaticBidPriceAndVolume()
   410  	bestStaticOfferPrice, bestStaticOfferVolume, _ := m.getBestStaticAskPriceAndVolume()
   411  
   412  	// Auction related values
   413  	indicativePrice := num.UintZero()
   414  	indicativeVolume := uint64(0)
   415  	var auctionStart, auctionEnd int64
   416  	if m.as.InAuction() {
   417  		indicativePrice, indicativeVolume, _ = m.matching.GetIndicativePriceAndVolume()
   418  		if t := m.as.Start(); !t.IsZero() {
   419  			auctionStart = t.UnixNano()
   420  		}
   421  		if t := m.as.ExpiresAt(); t != nil {
   422  			auctionEnd = t.UnixNano()
   423  		}
   424  	}
   425  
   426  	// If we do not have one of the best_* prices, leave the mid price as zero
   427  	two := num.NewUint(2)
   428  	midPrice := num.UintZero()
   429  	if !bestBidPrice.IsZero() && !bestOfferPrice.IsZero() {
   430  		midPrice = midPrice.Div(num.Sum(bestBidPrice, bestOfferPrice), two)
   431  	}
   432  
   433  	staticMidPrice := num.UintZero()
   434  	if !bestStaticBidPrice.IsZero() && !bestStaticOfferPrice.IsZero() {
   435  		staticMidPrice = staticMidPrice.Div(num.Sum(bestStaticBidPrice, bestStaticOfferPrice), two)
   436  	}
   437  
   438  	targetStake := m.getTargetStake().String()
   439  	bounds := m.pMonitor.GetBounds()
   440  	for _, b := range bounds {
   441  		b.MaxValidPrice = m.priceToMarketPrecision(b.MaxValidPrice) // effictively floors this
   442  		b.MinValidPrice = m.priceToMarketPrecision(b.MinValidPrice)
   443  
   444  		rp, _ := num.UintFromDecimal(b.ReferencePrice)
   445  		rp = m.priceToMarketPrecision(rp)
   446  		b.ReferencePrice = num.DecimalFromUint(rp)
   447  
   448  		if m.priceFactor.GreaterThan(num.DecimalOne()) {
   449  			b.MinValidPrice.AddSum(common.One) // ceil
   450  		}
   451  	}
   452  	mode := m.as.Mode()
   453  	if m.mkt.TradingMode == types.MarketTradingModeNoTrading {
   454  		mode = m.mkt.TradingMode
   455  	}
   456  
   457  	var papState *vega.ProtocolAutomatedPurchaseData
   458  	if m.pap != nil {
   459  		var activeOrder *string
   460  		if len(m.pap.activeOrder) > 0 {
   461  			activeOrder = &m.pap.activeOrder
   462  		}
   463  		papState = &vega.ProtocolAutomatedPurchaseData{
   464  			Id:      m.pap.ID,
   465  			OrderId: activeOrder,
   466  		}
   467  	}
   468  
   469  	return types.MarketData{
   470  		Market:                    m.GetID(),
   471  		BestBidPrice:              m.priceToMarketPrecision(bestBidPrice),
   472  		BestBidVolume:             bestBidVolume,
   473  		BestOfferPrice:            m.priceToMarketPrecision(bestOfferPrice),
   474  		BestOfferVolume:           bestOfferVolume,
   475  		BestStaticBidPrice:        m.priceToMarketPrecision(bestStaticBidPrice),
   476  		BestStaticBidVolume:       bestStaticBidVolume,
   477  		BestStaticOfferPrice:      m.priceToMarketPrecision(bestStaticOfferPrice),
   478  		BestStaticOfferVolume:     bestStaticOfferVolume,
   479  		NextMTM:                   m.nextMTM.UnixNano(),
   480  		MidPrice:                  m.priceToMarketPrecision(midPrice),
   481  		StaticMidPrice:            m.priceToMarketPrecision(staticMidPrice),
   482  		MarkPrice:                 m.priceToMarketPrecision(m.getCurrentMarkPrice()),
   483  		LastTradedPrice:           m.priceToMarketPrecision(m.getLastTradedPrice()),
   484  		Timestamp:                 m.timeService.GetTimeNow().UnixNano(),
   485  		IndicativePrice:           m.priceToMarketPrecision(indicativePrice),
   486  		IndicativeVolume:          indicativeVolume,
   487  		AuctionStart:              auctionStart,
   488  		AuctionEnd:                auctionEnd,
   489  		MarketTradingMode:         mode,
   490  		MarketState:               m.mkt.State,
   491  		Trigger:                   m.as.Trigger(),
   492  		ExtensionTrigger:          m.as.ExtensionTrigger(),
   493  		TargetStake:               targetStake,
   494  		SuppliedStake:             m.getSuppliedStake().String(),
   495  		PriceMonitoringBounds:     bounds,
   496  		MarketValueProxy:          m.lastMarketValueProxy.BigInt().String(),
   497  		LiquidityProviderFeeShare: m.equityShares.LpsToLiquidityProviderFeeShare(m.liquidity.GetAverageLiquidityScores()),
   498  		LiquidityProviderSLA:      m.liquidityEngine.LiquidityProviderSLAStats(m.timeService.GetTimeNow()),
   499  		PAPState:                  papState,
   500  	}
   501  }
   502  
   503  func (m *Market) uncrossOnLeaveAuction(ctx context.Context) ([]*types.OrderConfirmation, []*types.Order) {
   504  	uncrossedOrders, ordersToCancel, err := m.matching.LeaveAuction(m.timeService.GetTimeNow())
   505  	if err != nil {
   506  		m.log.Error("Error leaving auction", logging.Error(err))
   507  	}
   508  	evts := make([]events.Event, 0, len(uncrossedOrders))
   509  	for _, uncrossedOrder := range uncrossedOrders {
   510  		m.handleConfirmation(ctx, uncrossedOrder)
   511  		if uncrossedOrder.Order.Remaining == 0 {
   512  			uncrossedOrder.Order.Status = types.OrderStatusFilled
   513  		}
   514  		evts = append(evts, events.NewOrderEvent(ctx, uncrossedOrder.Order))
   515  	}
   516  
   517  	for _, uncrossedOrder := range uncrossedOrders {
   518  		m.handleConfirmationPassiveOrders(ctx, uncrossedOrder)
   519  	}
   520  
   521  	// send order events in a single batch, it's more efficient
   522  	m.broker.SendBatch(evts)
   523  	for _, otc := range ordersToCancel {
   524  		if otc.Party == types.NetworkParty {
   525  			m.papOrderProcessingEnded(otc.ID)
   526  		}
   527  	}
   528  	for _, otc := range uncrossedOrders {
   529  		if otc.Order.ID == types.NetworkParty {
   530  			m.papOrderProcessingEnded(otc.Order.ID)
   531  		}
   532  		for _, t := range otc.Trades {
   533  			if t.Seller == types.NetworkParty {
   534  				m.papOrderProcessingEnded(t.SellOrder)
   535  			} else if t.Buyer == types.NetworkParty {
   536  				m.papOrderProcessingEnded(t.BuyOrder)
   537  			}
   538  		}
   539  	}
   540  
   541  	return uncrossedOrders, ordersToCancel
   542  }
   543  
   544  func (m *Market) uncrossOrderAtAuctionEnd(ctx context.Context) {
   545  	if !m.as.InAuction() || m.as.IsOpeningAuction() {
   546  		return
   547  	}
   548  	m.uncrossOnLeaveAuction(ctx)
   549  }
   550  
   551  func (m *Market) EnterLongBlockAuction(ctx context.Context, duration int64) {
   552  	if !m.canTrade() {
   553  		return
   554  	}
   555  
   556  	// markets in governance auction are unaffected by long block auctions.
   557  	if m.mkt.TradingMode == types.MarketTradingModeSuspendedViaGovernance {
   558  		return
   559  	}
   560  
   561  	if m.as.InAuction() {
   562  		now := m.timeService.GetTimeNow()
   563  		aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second)
   564  		if aRemaining >= duration {
   565  			return
   566  		}
   567  		m.as.ExtendAuctionLongBlock(types.AuctionDuration{
   568  			Duration: duration - aRemaining,
   569  		})
   570  		if evt := m.as.AuctionExtended(ctx, now); evt != nil {
   571  			m.broker.Send(evt)
   572  		}
   573  	} else {
   574  		m.as.StartLongBlockAuction(m.timeService.GetTimeNow(), duration)
   575  		m.mkt.TradingMode = types.MarketTradingModeLongBlockAuction
   576  		m.mkt.State = types.MarketStateSuspended
   577  		m.enterAuction(ctx)
   578  		m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
   579  	}
   580  }
   581  
   582  func (m *Market) UpdateMarketState(ctx context.Context, changes *types.MarketStateUpdateConfiguration) error {
   583  	_, blockHash := vegacontext.TraceIDFromContext(ctx)
   584  	// make deterministic ID for this market, concatenate
   585  	// the block hash and the market ID
   586  	m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID()))
   587  	// and we call next ID on this directly just so we don't have an ID which have
   588  	// a different from others, we basically burn the first ID.
   589  	_ = m.idgen.NextID()
   590  	defer func() { m.idgen = nil }()
   591  	if changes.UpdateType == types.MarketStateUpdateTypeTerminate {
   592  		m.uncrossOrderAtAuctionEnd(ctx)
   593  		// terminate and settle
   594  		m.closeSpotMarket(ctx)
   595  	} else if changes.UpdateType == types.MarketStateUpdateTypeSuspend {
   596  		m.mkt.State = types.MarketStateSuspendedViaGovernance
   597  		m.mkt.TradingMode = types.MarketTradingModeSuspendedViaGovernance
   598  		if m.as.InAuction() {
   599  			m.as.ExtendAuctionSuspension(types.AuctionDuration{Duration: int64(m.minDuration.Seconds())})
   600  			evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow())
   601  			if evt != nil {
   602  				m.broker.Send(evt)
   603  			}
   604  		} else {
   605  			m.as.StartGovernanceSuspensionAuction(m.timeService.GetTimeNow())
   606  			m.enterAuction(ctx)
   607  			m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
   608  		}
   609  	} else if changes.UpdateType == types.MarketStateUpdateTypeResume && m.mkt.State == types.MarketStateSuspendedViaGovernance {
   610  		if m.as.GetState().Trigger == types.AuctionTriggerGovernanceSuspension && m.as.GetState().Extension == types.AuctionTriggerUnspecified {
   611  			m.as.EndGovernanceSuspensionAuction()
   612  			m.leaveAuction(ctx, m.timeService.GetTimeNow())
   613  		} else {
   614  			m.as.EndGovernanceSuspensionAuction()
   615  			if m.as.GetState().Trigger == types.AuctionTriggerOpening {
   616  				m.mkt.State = types.MarketStatePending
   617  				m.mkt.TradingMode = types.MarketTradingModeOpeningAuction
   618  			} else {
   619  				m.mkt.State = types.MarketStateSuspended
   620  				m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction
   621  			}
   622  			m.checkAuction(ctx, m.timeService.GetTimeNow(), m.idgen)
   623  			m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
   624  		}
   625  	}
   626  	return nil
   627  }
   628  
   629  // ReloadConf will trigger a reload of all the config settings in the market and all underlying engines
   630  // this is required when hot-reloading any config changes, eg. logger level.
   631  func (m *Market) ReloadConf(matchingConfig matching.Config, feeConfig fee.Config) {
   632  	m.log.Info("reloading configuration")
   633  	m.matching.ReloadConf(matchingConfig)
   634  	m.fee.ReloadConf(feeConfig)
   635  }
   636  
   637  func (m *Market) GetAssetForProposerBonus() string {
   638  	return m.quoteAsset
   639  }
   640  
   641  // Reject a market if the market state allow.
   642  func (m *Market) Reject(ctx context.Context) error {
   643  	if m.mkt.State != types.MarketStateProposed {
   644  		return common.ErrCannotRejectMarketNotInProposedState
   645  	}
   646  
   647  	// we closed all parties accounts
   648  	m.cleanupOnReject(ctx)
   649  	m.mkt.State = types.MarketStateRejected
   650  	m.mkt.TradingMode = types.MarketTradingModeNoTrading
   651  	m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
   652  	return nil
   653  }
   654  
   655  // CanLeaveOpeningAuction checks if the market can leave the opening auction based on whether floating point consensus has been reached on all 2 vars.
   656  func (m *Market) CanLeaveOpeningAuction() bool {
   657  	boundFactorsInitialised := m.pMonitor.IsBoundFactorsInitialised()
   658  	potInitialised := m.liquidity.IsProbabilityOfTradingInitialised()
   659  
   660  	canLeave := boundFactorsInitialised && potInitialised
   661  	if !canLeave {
   662  		m.log.Info("Cannot leave opening auction", logging.String("market", m.mkt.ID), logging.Bool("bound-factors-initialised", boundFactorsInitialised), logging.Bool("pot-initialised", potInitialised))
   663  	}
   664  	return canLeave
   665  }
   666  
   667  // StartOpeningAuction kicks off opening auction.
   668  func (m *Market) StartOpeningAuction(ctx context.Context) error {
   669  	if m.mkt.State != types.MarketStateProposed {
   670  		return common.ErrCannotStartOpeningAuctionForMarketNotInProposedState
   671  	}
   672  
   673  	// now we start the opening auction
   674  	if m.as.AuctionStart() {
   675  		// we are now in a pending state
   676  		m.mkt.State = types.MarketStatePending
   677  		m.mkt.TradingMode = types.MarketTradingModeOpeningAuction
   678  		m.enterAuction(ctx)
   679  	} else {
   680  		m.mkt.State = types.MarketStateActive
   681  		m.mkt.TradingMode = types.MarketTradingModeContinuous
   682  	}
   683  
   684  	m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
   685  	return nil
   686  }
   687  
   688  // GetID returns the id of the given market.
   689  func (m *Market) GetID() string {
   690  	return m.mkt.ID
   691  }
   692  
   693  // PostRestore restores market price in orders after snapshot reload.
   694  func (m *Market) PostRestore(ctx context.Context) error {
   695  	// tell the matching engine about the markets price factor so it can finish restoring orders
   696  	m.matching.RestoreWithMarketPriceFactor(m.priceFactor)
   697  	return nil
   698  }
   699  
   700  // OnTick notifies the market of a new time event/update.
   701  func (m *Market) OnTick(ctx context.Context, t time.Time) bool {
   702  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "OnTick")
   703  	m.mu.Lock()
   704  	defer m.mu.Unlock()
   705  
   706  	_, blockHash := vegacontext.TraceIDFromContext(ctx)
   707  	// make deterministic ID for this market, concatenate
   708  	// the block hash and the market ID
   709  	m.idgen = idgeneration.New(blockHash + crypto.HashStrToHex(m.GetID()))
   710  	// and we call next ID on this directly just so we don't have an ID which have
   711  	// a different from others, we basically burn the first ID.
   712  	_ = m.idgen.NextID()
   713  	defer func() { m.idgen = nil }()
   714  
   715  	if m.closed {
   716  		return true
   717  	}
   718  
   719  	m.checkPAP(ctx)
   720  
   721  	// first we expire orders
   722  	if !m.closed && m.canTrade() {
   723  		expired := m.removeExpiredOrders(ctx, t.UnixNano())
   724  		metrics.OrderGaugeAdd(-len(expired), m.GetID())
   725  		confirmations := m.removeExpiredStopOrders(ctx, t.UnixNano(), m.idgen)
   726  
   727  		stopsExpired := 0
   728  		for _, v := range confirmations {
   729  			stopsExpired++
   730  			for _, v := range v.PassiveOrdersAffected {
   731  				if v.Status != types.OrderStatusActive {
   732  					stopsExpired++
   733  				}
   734  			}
   735  		}
   736  		metrics.OrderGaugeAdd(-stopsExpired, m.GetID())
   737  	}
   738  
   739  	// some engines still needs to get updates:
   740  	m.pMonitor.OnTimeUpdate(t)
   741  	m.feeSplitter.SetCurrentTime(t)
   742  
   743  	if m.mkt.State == types.MarketStateProposed {
   744  		return false
   745  	}
   746  
   747  	m.checkAuction(ctx, t, m.idgen)
   748  	timer.EngineTimeCounterAdd()
   749  	m.updateMarketValueProxy()
   750  	m.updateLiquidityFee(ctx)
   751  	m.liquidity.OnTick(ctx, t)
   752  	m.broker.Send(events.NewMarketTick(ctx, m.mkt.ID, t))
   753  	return m.closed
   754  }
   755  
   756  // BlockEnd notifies the market of the end of the block.
   757  func (m *Market) BlockEnd(ctx context.Context) {
   758  	t := m.timeService.GetTimeNow()
   759  	if m.mkt.State == types.MarketStateProposed ||
   760  		m.mkt.State == types.MarketStateCancelled ||
   761  		m.mkt.State == types.MarketStateRejected ||
   762  		m.mkt.State == types.MarketStateSettled {
   763  		if (m.nextMTM.IsZero() || !m.nextMTM.After(t)) && !m.as.InAuction() {
   764  			m.nextMTM = t.Add(m.mtmDelta)
   765  		}
   766  		return
   767  	}
   768  	// simplified version of updating mark price every MTM interval
   769  	mp := m.getLastTradedPrice()
   770  	if !m.hasTraded && m.markPrice != nil {
   771  		// no trades happened, make sure we're just using the current mark price
   772  		mp = m.markPrice.Clone()
   773  	}
   774  
   775  	if !mp.IsZero() && !m.as.InAuction() && (m.nextMTM.IsZero() || !m.nextMTM.After(t)) {
   776  		m.pMonitor.CheckPrice(ctx, m.as, mp, true, true)
   777  		if !m.as.InAuction() && !m.as.AuctionStart() {
   778  			m.markPriceLock.Lock()
   779  			m.markPrice = mp
   780  			m.markPriceLock.Unlock()
   781  			m.lastTradedPrice = mp.Clone()
   782  			m.hasTraded = false
   783  		}
   784  		m.nextMTM = t.Add(m.mtmDelta)
   785  	}
   786  	m.tsCalc.RecordTotalStake(m.liquidity.CalculateSuppliedStake().Uint64(), m.timeService.GetTimeNow())
   787  	m.liquidity.EndBlock(m.markPrice, m.midPrice(), m.positionFactor)
   788  }
   789  
   790  func (m *Market) updateMarketValueProxy() {
   791  	// if windows length is reached, reset fee splitter
   792  	if mvwl := m.marketValueWindowLength; m.feeSplitter.Elapsed() > mvwl {
   793  		// AvgTradeValue calculates the rolling average trade value to include the current window (which is ending)
   794  		m.equityShares.AvgTradeValue(m.feeSplitter.AvgTradeValue())
   795  		// this increments the internal window counter
   796  		m.feeSplitter.TimeWindowStart(m.timeService.GetTimeNow())
   797  		// m.equityShares.UpdateVirtualStake() // this should always set the vStake >= physical stake?
   798  	}
   799  
   800  	// these need to happen every block
   801  	// but also when new LP is submitted just so we are sure we do
   802  	// not have a mvp of 0
   803  	ts := m.liquidity.CalculateSuppliedStake()
   804  	m.lastMarketValueProxy = m.feeSplitter.MarketValueProxy(
   805  		m.marketValueWindowLength, ts)
   806  }
   807  
   808  // removeOrders removes orders from the book when the market is stopped.
   809  func (m *Market) removeOrders(ctx context.Context) {
   810  	// remove all order from the book
   811  	// and send events with the stopped status
   812  	orders := append(m.matching.Settled(), m.peggedOrders.Settled()...)
   813  	orderEvents := make([]events.Event, 0, len(orders))
   814  	for _, v := range orders {
   815  		orderEvents = append(orderEvents, events.NewOrderEvent(ctx, v))
   816  		// release any locked funds for the order from the holding account
   817  		m.releaseOrderFromHoldingAccount(ctx, v.ID, v.Party, v.Side)
   818  	}
   819  	m.broker.SendBatch(orderEvents)
   820  }
   821  
   822  // cleanMarketWithState clears the collateral state of the market and clears up state vars and sets the terminated state of the market
   823  // NB: should it actually go to settled?.
   824  func (m *Market) cleanMarketWithState(ctx context.Context, mktState types.MarketState) error {
   825  	clearMarketTransfers, err := m.collateral.ClearSpotMarket(ctx, m.GetID(), m.quoteAsset, m.getParties())
   826  	if err != nil {
   827  		m.log.Error("Clear market error",
   828  			logging.MarketID(m.GetID()),
   829  			logging.Error(err))
   830  		return err
   831  	}
   832  
   833  	m.stateVarEngine.UnregisterStateVariable(m.quoteAsset, m.mkt.ID)
   834  	if len(clearMarketTransfers) > 0 {
   835  		m.broker.Send(events.NewLedgerMovements(ctx, clearMarketTransfers))
   836  	}
   837  
   838  	m.mkt.State = mktState
   839  	m.mkt.TradingMode = types.MarketTradingModeNoTrading
   840  	m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
   841  	m.closed = true
   842  	return nil
   843  }
   844  
   845  // closeCancelledMarket cleans up after a cancelled market.
   846  func (m *Market) closeCancelledMarket(ctx context.Context) error {
   847  	if err := m.cleanMarketWithState(ctx, types.MarketStateCancelled); err != nil {
   848  		return err
   849  	}
   850  
   851  	m.liquidity.StopAllLiquidityProvision(ctx)
   852  	m.closed = true
   853  	return nil
   854  }
   855  
   856  // closeMarket
   857  // NB: this is currently called immediately from terminate trading.
   858  func (m *Market) closeMarket(ctx context.Context) error {
   859  	// final distribution of liquidity fees
   860  	m.liquidity.OnMarketClosed(ctx, m.timeService.GetTimeNow())
   861  	// final distribution of liquidity fees
   862  	if !m.finalFeesDistributed {
   863  		if err := m.liquidity.AllocateFees(ctx); err != nil {
   864  			m.log.Panic("failed to allocate liquidity provision fees", logging.Error(err))
   865  		}
   866  
   867  		m.liquidity.OnEpochEnd(ctx, m.timeService.GetTimeNow(), m.epoch)
   868  		m.finalFeesDistributed = true
   869  	}
   870  	err := m.cleanMarketWithState(ctx, types.MarketStateClosed)
   871  	if err != nil {
   872  		return err
   873  	}
   874  
   875  	m.removeOrders(ctx)
   876  	m.liquidity.StopAllLiquidityProvision(ctx)
   877  	return nil
   878  }
   879  
   880  // unregisterAndReject - the order didn't go to the book therefore there's no need to release funds from the holding account.
   881  func (m *Market) unregisterAndReject(ctx context.Context, order *types.Order, err error) error {
   882  	order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
   883  	order.Status = types.OrderStatusRejected
   884  	if oerr, ok := types.IsOrderError(err); ok {
   885  		// the order wasn't invalid, so stopped is a better status, rather than rejected.
   886  		if types.IsStoppingOrder(oerr) {
   887  			order.Status = types.OrderStatusStopped
   888  		}
   889  		order.Reason = oerr
   890  	} else {
   891  		// should not happened but still...
   892  		order.Reason = types.OrderErrorInternalError
   893  	}
   894  	m.broker.Send(events.NewOrderEvent(ctx, order))
   895  	if m.log.GetLevel() == logging.DebugLevel {
   896  		m.log.Debug("Failure after submitting order to matching engine",
   897  			logging.Order(*order),
   898  			logging.Error(err))
   899  	}
   900  	return err
   901  }
   902  
   903  // getNewPeggedPrice calculates pegged price based on the pegged reference and current prices.
   904  func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) {
   905  	if m.as.InAuction() {
   906  		return num.UintZero(), common.ErrCannotRepriceDuringAuction
   907  	}
   908  
   909  	var (
   910  		err   error
   911  		price *num.Uint
   912  	)
   913  
   914  	switch order.PeggedOrder.Reference {
   915  	case types.PeggedReferenceMid:
   916  		price, err = m.getStaticMidPrice(order.Side)
   917  	case types.PeggedReferenceBestBid:
   918  		price, err = m.getBestStaticBidPrice()
   919  	case types.PeggedReferenceBestAsk:
   920  		price, err = m.getBestStaticAskPrice()
   921  	}
   922  	if err != nil {
   923  		return num.UintZero(), common.ErrUnableToReprice
   924  	}
   925  
   926  	// we're converting both offset and tick size to asset decimals so we can adjust the price (in asset) directly
   927  	priceInMarket, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))
   928  	if order.Side == types.SideSell {
   929  		priceInMarket.AddSum(order.PeggedOrder.Offset)
   930  		// this can only happen when pegged to mid, in which case we want to round to the nearest *better* tick size
   931  		// but this can never cross the mid by construction as the the minimum offset is 1 tick size and all prices must be
   932  		// whole multiples of tick size.
   933  		if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
   934  			priceInMarket.Sub(priceInMarket, mod)
   935  		}
   936  		price, _ := num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
   937  		return price, nil
   938  	}
   939  
   940  	if priceInMarket.LTE(order.PeggedOrder.Offset) {
   941  		return num.UintZero(), common.ErrUnableToReprice
   942  	}
   943  
   944  	priceInMarket.Sub(priceInMarket, order.PeggedOrder.Offset)
   945  	if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
   946  		priceInMarket = num.UintZero().Sub(priceInMarket.AddSum(m.mkt.TickSize), mod)
   947  	}
   948  	price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
   949  
   950  	return price, nil
   951  }
   952  
   953  // Reprice a pegged order. This only updates the price on the order.
   954  func (m *Market) repricePeggedOrder(order *types.Order) error {
   955  	// Work out the new price of the order
   956  	price, err := m.getNewPeggedPrice(order)
   957  	if err != nil {
   958  		return err
   959  	}
   960  	order.OriginalPrice, _ = num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor)) // set original price in market precision
   961  	order.Price = price
   962  	return nil
   963  }
   964  
   965  // parkAllPeggedOrders parks all pegged orders.
   966  func (m *Market) parkAllPeggedOrders(ctx context.Context) {
   967  	toParkIDs := m.matching.GetActivePeggedOrderIDs()
   968  	for _, order := range toParkIDs {
   969  		m.parkOrder(ctx, order)
   970  	}
   971  }
   972  
   973  // EnterAuction : Prepare the order book to be run as an auction.
   974  // when entering an auction we need to make sure there's sufficient funds in the holding account to cover the potential trade + fees.
   975  // If there isn't, the order must be cancelled.
   976  func (m *Market) enterAuction(ctx context.Context) {
   977  	// Change market type to auction
   978  	ordersToCancel := m.matching.EnterAuction()
   979  
   980  	// Move into auction mode to prevent pegged order repricing
   981  	event := m.as.AuctionStarted(ctx, m.timeService.GetTimeNow())
   982  
   983  	// Cancel all the orders that were invalid
   984  	for _, order := range ordersToCancel {
   985  		_, err := m.cancelOrder(ctx, order.Party, order.ID)
   986  		if err != nil {
   987  			m.log.Debug("error cancelling order when entering auction",
   988  				logging.MarketID(m.GetID()),
   989  				logging.OrderID(order.ID),
   990  				logging.Error(err))
   991  		}
   992  	}
   993  
   994  	// now update all special orders
   995  	m.enterAuctionSpecialOrders(ctx)
   996  
   997  	// now that all orders that don't fit in auctions have been cancelled, process necessary transfer of fees from the general account of the
   998  	// buyers to the holding account. Orders with insufficient cover of buyer or where the quantity to be delivered to the seller does not cover
   999  	// for the due fees during auction are cancelled here.
  1000  	m.processFeesTransfersOnEnterAuction(ctx)
  1001  
  1002  	// Send an event bus update
  1003  	m.broker.Send(event)
  1004  
  1005  	if m.as.InAuction() && m.as.IsPriceAuction() {
  1006  		m.mkt.State = types.MarketStateSuspended
  1007  		m.mkt.TradingMode = types.MarketTradingModeMonitoringAuction
  1008  		m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1009  	}
  1010  }
  1011  
  1012  // OnOpeningAuctionFirstUncrossingPrice is triggered when the opening auction sees an uncrossing price for the first time and emits
  1013  // an event to the state variable engine.
  1014  func (m *Market) OnOpeningAuctionFirstUncrossingPrice() {
  1015  	m.log.Info("OnOpeningAuctionFirstUncrossingPrice event fired", logging.String("market", m.mkt.ID))
  1016  	m.stateVarEngine.ReadyForTimeTrigger(m.quoteAsset, m.mkt.ID)
  1017  	m.stateVarEngine.NewEvent(m.quoteAsset, m.mkt.ID, statevar.EventTypeOpeningAuctionFirstUncrossingPrice)
  1018  }
  1019  
  1020  // OnAuctionEnded is called whenever an auction is ended and emits an event to the state var engine.
  1021  func (m *Market) OnAuctionEnded() {
  1022  	m.log.Info("OnAuctionEnded event fired", logging.String("market", m.mkt.ID))
  1023  	m.stateVarEngine.NewEvent(m.quoteAsset, m.mkt.ID, statevar.EventTypeAuctionEnded)
  1024  }
  1025  
  1026  // leaveAuction : Return the orderbook and market to continuous trading.
  1027  func (m *Market) leaveAuction(ctx context.Context, now time.Time) {
  1028  	defer func() {
  1029  		if !m.as.InAuction() && (m.mkt.State == types.MarketStateSuspended || m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateSuspendedViaGovernance) {
  1030  			if m.mkt.State == types.MarketStatePending {
  1031  				// the market is now properly open,
  1032  				// so set the timestamp to when the opening auction actually ended
  1033  				m.mkt.MarketTimestamps.Open = now.UnixNano()
  1034  			}
  1035  			if m.mkt.TradingMode != types.MarketTradingModeOpeningAuction {
  1036  				// if we're leaving a price monitoring auction we can release the fees funds locked for the duration of the auction for any uncrossed orders
  1037  				m.processFeesReleaseOnLeaveAuction(ctx)
  1038  			}
  1039  			m.mkt.State = types.MarketStateActive
  1040  			m.mkt.TradingMode = types.MarketTradingModeContinuous
  1041  			m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  1042  			m.updateLiquidityFee(ctx)
  1043  			m.OnAuctionEnded()
  1044  		}
  1045  	}()
  1046  
  1047  	// Check here that the orders are still valid in terms of holding amounts
  1048  	m.checkFeeTransfersWhileInAuction(ctx)
  1049  
  1050  	_, ordersToCancel := m.uncrossOnLeaveAuction(ctx)
  1051  
  1052  	// Process each order we have to cancel
  1053  	for _, order := range ordersToCancel {
  1054  		_, err := m.cancelOrder(ctx, order.Party, order.ID)
  1055  		if err != nil {
  1056  			m.log.Panic("Failed to cancel order",
  1057  				logging.Error(err),
  1058  				logging.String("OrderID", order.ID))
  1059  		}
  1060  	}
  1061  
  1062  	// update auction state, so we know what the new tradeMode ought to be
  1063  	endEvt := m.as.Left(ctx, now)
  1064  
  1065  	previousMarkPrice := m.getCurrentMarkPrice()
  1066  	// set the mark price here so that margins checks for special orders use the correct value
  1067  	m.markPriceLock.Lock()
  1068  	m.markPrice = m.getLastTradedPrice()
  1069  	m.markPriceLock.Unlock()
  1070  
  1071  	m.checkForReferenceMoves(ctx, true)
  1072  	if !m.as.InAuction() {
  1073  		// only send the auction-left event if we actually *left* the auction.
  1074  		m.broker.Send(endEvt)
  1075  		m.nextMTM = m.timeService.GetTimeNow().Add(m.mtmDelta)
  1076  	} else {
  1077  		// revert to old mark price if we're not leaving the auction after all
  1078  		m.markPriceLock.Lock()
  1079  		m.markPrice = previousMarkPrice
  1080  		m.markPriceLock.Unlock()
  1081  	}
  1082  }
  1083  
  1084  // validateOrder checks that the order parameters are valid for the market.
  1085  // NB: price in market, tickSize in market decimals.
  1086  func (m *Market) validateOrder(ctx context.Context, order *types.Order) (err error) {
  1087  	defer func() {
  1088  		if err != nil {
  1089  			order.Status = types.OrderStatusRejected
  1090  			m.broker.Send(events.NewOrderEvent(ctx, order))
  1091  		}
  1092  	}()
  1093  
  1094  	// Check we are allowed to handle this order type with the current market status
  1095  	isAuction := m.as.InAuction()
  1096  	if isAuction && order.TimeInForce == types.OrderTimeInForceGFN {
  1097  		order.Status = types.OrderStatusRejected
  1098  		order.Reason = types.OrderErrorCannotSendGFNOrderDuringAnAuction
  1099  		return common.ErrGFNOrderReceivedAuctionTrading
  1100  	}
  1101  
  1102  	if isAuction && order.TimeInForce == types.OrderTimeInForceIOC {
  1103  		order.Reason = types.OrderErrorCannotSendIOCOrderDuringAuction
  1104  		return common.ErrIOCOrderReceivedAuctionTrading
  1105  	}
  1106  
  1107  	if isAuction && order.TimeInForce == types.OrderTimeInForceFOK {
  1108  		order.Reason = types.OrderErrorCannotSendFOKOrderDurinAuction
  1109  		return common.ErrFOKOrderReceivedAuctionTrading
  1110  	}
  1111  
  1112  	if !isAuction && order.TimeInForce == types.OrderTimeInForceGFA {
  1113  		order.Reason = types.OrderErrorGFAOrderDuringContinuousTrading
  1114  		return common.ErrGFAOrderReceivedDuringContinuousTrading
  1115  	}
  1116  
  1117  	// Check the expiry time is valid
  1118  	if order.ExpiresAt > 0 && order.ExpiresAt < order.CreatedAt {
  1119  		order.Reason = types.OrderErrorInvalidExpirationDatetime
  1120  		return common.ErrInvalidExpiresAtTime
  1121  	}
  1122  
  1123  	if m.closed {
  1124  		// adding order to the buffer first
  1125  		order.Reason = types.OrderErrorMarketClosed
  1126  		return common.ErrMarketClosed
  1127  	}
  1128  
  1129  	if order.Type == types.OrderTypeNetwork {
  1130  		order.Reason = types.OrderErrorInvalidType
  1131  		return common.ErrInvalidOrderType
  1132  	}
  1133  
  1134  	// Validate market
  1135  	if order.MarketID != m.mkt.ID {
  1136  		// adding order to the buffer first
  1137  		order.Reason = types.OrderErrorInvalidMarketID
  1138  		if m.log.GetLevel() == logging.DebugLevel {
  1139  			m.log.Debug("Market ID mismatch",
  1140  				logging.Order(*order),
  1141  				logging.String("market", m.mkt.ID))
  1142  		}
  1143  		return types.ErrInvalidMarketID
  1144  	}
  1145  
  1146  	// Validate pegged orders
  1147  	if order.PeggedOrder != nil {
  1148  		if reason := order.ValidatePeggedOrder(); reason != types.OrderErrorUnspecified {
  1149  			order.Reason = reason
  1150  			if m.log.GetLevel() == logging.DebugLevel {
  1151  				m.log.Debug("Failed to validate pegged order details",
  1152  					logging.Order(*order),
  1153  					logging.String("market", m.mkt.ID))
  1154  			}
  1155  			return reason
  1156  		}
  1157  		if order.PeggedOrder.Reference == types.PeggedReferenceMid {
  1158  			offsetInAsset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor))
  1159  			tickSizeInAsset, _ := num.UintFromDecimal(m.mkt.TickSize.ToDecimal().Mul(m.priceFactor))
  1160  			if offsetInAsset.IsZero() && tickSizeInAsset.IsZero() {
  1161  				return fmt.Errorf("invalid offset - pegged mid will cross")
  1162  			}
  1163  		}
  1164  		return m.validateTickSize(order.PeggedOrder.Offset)
  1165  	}
  1166  
  1167  	if order.OriginalPrice != nil {
  1168  		return m.validateTickSize(order.OriginalPrice)
  1169  	}
  1170  
  1171  	return nil
  1172  }
  1173  
  1174  func (m *Market) validateTickSize(price *num.Uint) error {
  1175  	d := num.UintZero().Mod(price, m.mkt.TickSize)
  1176  	if !d.IsZero() {
  1177  		return types.ErrOrderNotInTickSize
  1178  	}
  1179  	return nil
  1180  }
  1181  
  1182  // validateAccounts checks that the party has the required accounts and that they have sufficient funds in the account to cover for the trade and
  1183  // any fees due.
  1184  func (m *Market) validateAccounts(ctx context.Context, order *types.Order) error {
  1185  	if order.Party == types.NetworkParty {
  1186  		return nil
  1187  	}
  1188  	if (order.Side == types.SideBuy && !m.collateral.HasGeneralAccount(order.Party, m.quoteAsset)) ||
  1189  		(order.Side == types.SideSell && !m.collateral.HasGeneralAccount(order.Party, m.baseAsset)) {
  1190  		// adding order to the buffer first
  1191  		order.Status = types.OrderStatusRejected
  1192  		order.Reason = types.OrderErrorInsufficientAssetBalance
  1193  		m.broker.Send(events.NewOrderEvent(ctx, order))
  1194  
  1195  		// party should be created before even trying to post order
  1196  		return common.ErrPartyInsufficientAssetBalance
  1197  	}
  1198  
  1199  	price := order.Price
  1200  	// pegged order would not have a price at this point so unless we're in auction we need to get a price for it first
  1201  	if order.PeggedOrder != nil && !m.as.InAuction() {
  1202  		p, err := m.getNewPeggedPrice(order)
  1203  		if err != nil {
  1204  			return err
  1205  		}
  1206  		price = p
  1207  	}
  1208  	// if the order is not pegged or it is pegged and we're not in an auction, check the party has sufficient balance
  1209  	if order.PeggedOrder == nil || !m.as.InAuction() {
  1210  		accType := types.AccountTypeGeneral
  1211  		if order.Party == types.NetworkParty {
  1212  			var err error
  1213  			accType, _, err = m.getACcountTypesForPAP()
  1214  			if err != nil {
  1215  				m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", order.ID))
  1216  			}
  1217  		}
  1218  		if err := m.checkSufficientFunds(order.Party, order.Side, price, order.TrueRemaining(), order.PeggedOrder != nil, accType); err != nil {
  1219  			return err
  1220  		}
  1221  	}
  1222  
  1223  	// from this point we know the party have the necessary accounts and balances
  1224  	// we had it to the list of parties.
  1225  	m.addParty(order.Party)
  1226  	return nil
  1227  }
  1228  
  1229  func rejectStopOrders(rejectionReason types.StopOrderRejectionReason, orders ...*types.StopOrder) {
  1230  	for _, o := range orders {
  1231  		if o != nil {
  1232  			o.Status = types.StopOrderStatusRejected
  1233  			o.RejectionReason = ptr.From(rejectionReason)
  1234  		}
  1235  	}
  1236  }
  1237  
  1238  func (m *Market) SubmitStopOrdersWithIDGeneratorAndOrderIDs(
  1239  	ctx context.Context,
  1240  	submission *types.StopOrdersSubmission,
  1241  	party string,
  1242  	idgen common.IDGenerator,
  1243  	fallsBelowID, risesAboveID *string,
  1244  ) (*types.OrderConfirmation, error) {
  1245  	m.idgen = idgen
  1246  	defer func() { m.idgen = nil }()
  1247  
  1248  	fallsBelow, risesAbove := submission.IntoStopOrders(
  1249  		party, ptr.UnBox(fallsBelowID), ptr.UnBox(risesAboveID), m.timeService.GetTimeNow())
  1250  
  1251  	defer func() {
  1252  		evts := []events.Event{}
  1253  		if fallsBelow != nil {
  1254  			evts = append(evts, events.NewStopOrderEvent(ctx, fallsBelow))
  1255  		}
  1256  		if risesAbove != nil {
  1257  			evts = append(evts, events.NewStopOrderEvent(ctx, risesAbove))
  1258  		}
  1259  
  1260  		if len(evts) > 0 {
  1261  			m.broker.SendBatch(evts)
  1262  		}
  1263  	}()
  1264  
  1265  	if m.IsOpeningAuction() {
  1266  		rejectStopOrders(types.StopOrderRejectionNotAllowedDuringOpeningAuction, fallsBelow, risesAbove)
  1267  		return nil, common.ErrStopOrderNotAllowedDuringOpeningAuction
  1268  	}
  1269  
  1270  	if !m.canTrade() {
  1271  		rejectStopOrders(types.StopOrderRejectionTradingNotAllowed, fallsBelow, risesAbove)
  1272  		return nil, common.ErrTradingNotAllowed
  1273  	}
  1274  
  1275  	if fallsBelow != nil && fallsBelow.OrderSubmission != nil && !m.canSubmitMaybeSell(fallsBelow.Party, fallsBelow.OrderSubmission.Side) {
  1276  		rejectStopOrders(types.StopOrderRejectionSellOrderNotAllowed, fallsBelow, risesAbove)
  1277  		return nil, common.ErrSellOrderNotAllowed
  1278  	}
  1279  
  1280  	if risesAbove != nil && risesAbove.OrderSubmission != nil && !m.canSubmitMaybeSell(risesAbove.Party, risesAbove.OrderSubmission.Side) {
  1281  		rejectStopOrders(types.StopOrderRejectionSellOrderNotAllowed, risesAbove, risesAbove)
  1282  		return nil, common.ErrSellOrderNotAllowed
  1283  	}
  1284  
  1285  	now := m.timeService.GetTimeNow()
  1286  	orderCnt := 0
  1287  	if fallsBelow != nil {
  1288  		if fallsBelow.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition {
  1289  			rejectStopOrders(types.StopOrderRejectionSizeOverrideUnsupportedForSpot, fallsBelow, risesAbove)
  1290  			return nil, common.ErrStopOrderSizeOverrideNotSupportedForSpots
  1291  		}
  1292  		if fallsBelow.Expiry.Expires() && fallsBelow.Expiry.ExpiresAt.Before(now) {
  1293  			rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove)
  1294  			return nil, common.ErrStopOrderExpiryInThePast
  1295  		}
  1296  		if fallsBelow.OrderSubmission.Side == types.SideBuy && !m.collateral.HasGeneralAccount(party, m.quoteAsset) {
  1297  			rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove)
  1298  			return nil, common.ErrStopOrderSideNotClosingThePosition
  1299  		}
  1300  		if !m.collateral.HasGeneralAccount(party, m.baseAsset) {
  1301  			rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove)
  1302  			return nil, common.ErrStopOrderSideNotClosingThePosition
  1303  		}
  1304  		orderCnt++
  1305  	}
  1306  	if risesAbove != nil {
  1307  		if risesAbove.SizeOverrideSetting == types.StopOrderSizeOverrideSettingPosition {
  1308  			rejectStopOrders(types.StopOrderRejectionSizeOverrideUnsupportedForSpot, fallsBelow, risesAbove)
  1309  			return nil, common.ErrStopOrderSizeOverrideNotSupportedForSpots
  1310  		}
  1311  		if risesAbove.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Before(now) {
  1312  			rejectStopOrders(types.StopOrderRejectionExpiryInThePast, fallsBelow, risesAbove)
  1313  			return nil, common.ErrStopOrderExpiryInThePast
  1314  		}
  1315  		if risesAbove.OrderSubmission.Side == types.SideBuy && !m.collateral.HasGeneralAccount(party, m.quoteAsset) {
  1316  			rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove)
  1317  			return nil, common.ErrStopOrderSideNotClosingThePosition
  1318  		}
  1319  		if !m.collateral.HasGeneralAccount(party, m.baseAsset) {
  1320  			rejectStopOrders(types.StopOrderRejectionNotClosingThePosition, fallsBelow, risesAbove)
  1321  			return nil, common.ErrStopOrderSideNotClosingThePosition
  1322  		}
  1323  		orderCnt++
  1324  	}
  1325  
  1326  	if risesAbove != nil && fallsBelow != nil {
  1327  		if risesAbove.Expiry.Expires() && fallsBelow.Expiry.Expires() && risesAbove.Expiry.ExpiresAt.Compare(*fallsBelow.Expiry.ExpiresAt) == 0 {
  1328  			rejectStopOrders(types.StopOrderRejectionOCONotAllowedSameExpiryTime, fallsBelow, risesAbove)
  1329  			return nil, common.ErrStopOrderNotAllowedSameExpiry
  1330  		}
  1331  	}
  1332  
  1333  	// now check if that party hasn't exceeded the max amount per market
  1334  	if m.stopOrders.CountForParty(party)+uint64(orderCnt) > m.maxStopOrdersPerParties.Uint64() {
  1335  		rejectStopOrders(types.StopOrderRejectionMaxStopOrdersPerPartyReached, fallsBelow, risesAbove)
  1336  		return nil, common.ErrMaxStopOrdersPerPartyReached
  1337  	}
  1338  
  1339  	fallsBelowTriggered, risesAboveTriggered := m.stopOrderWouldTriggerAtSubmission(fallsBelow), m.stopOrderWouldTriggerAtSubmission(risesAbove)
  1340  	triggered := fallsBelowTriggered || risesAboveTriggered
  1341  
  1342  	// if we are in an auction
  1343  	// or no order is triggered
  1344  	// let's just submit it straight away
  1345  	if m.as.InAuction() || !triggered {
  1346  		m.poolStopOrders(fallsBelow, risesAbove)
  1347  		return nil, nil
  1348  	}
  1349  
  1350  	var confirmation *types.OrderConfirmation
  1351  	var err error
  1352  	// now would the order get trigger straight away?
  1353  	switch {
  1354  	case fallsBelowTriggered:
  1355  		fallsBelow.Status = types.StopOrderStatusTriggered
  1356  		if risesAbove != nil {
  1357  			risesAbove.Status = types.StopOrderStatusStopped
  1358  		}
  1359  		fallsBelow.OrderID = idgen.NextID()
  1360  		confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID(
  1361  			ctx, fallsBelow.OrderSubmission, party, idgen, fallsBelow.OrderID, true,
  1362  		)
  1363  		if err != nil && confirmation != nil {
  1364  			fallsBelow.OrderID = confirmation.Order.ID
  1365  		}
  1366  	case risesAboveTriggered:
  1367  		risesAbove.Status = types.StopOrderStatusTriggered
  1368  		if fallsBelow != nil {
  1369  			fallsBelow.Status = types.StopOrderStatusStopped
  1370  		}
  1371  		risesAbove.OrderID = idgen.NextID()
  1372  		confirmation, err = m.SubmitOrderWithIDGeneratorAndOrderID(
  1373  			ctx, risesAbove.OrderSubmission, party, idgen, risesAbove.OrderID, true,
  1374  		)
  1375  		if err != nil && confirmation != nil {
  1376  			risesAbove.OrderID = confirmation.Order.ID
  1377  		}
  1378  	}
  1379  
  1380  	return confirmation, err
  1381  }
  1382  
  1383  func (m *Market) poolStopOrders(
  1384  	fallsBelow, risesAbove *types.StopOrder,
  1385  ) {
  1386  	if fallsBelow != nil {
  1387  		m.stopOrders.Insert(fallsBelow)
  1388  		if fallsBelow.Expiry.Expires() {
  1389  			m.expiringStopOrders.Insert(fallsBelow.ID, fallsBelow.Expiry.ExpiresAt.UnixNano())
  1390  		}
  1391  	}
  1392  	if risesAbove != nil {
  1393  		m.stopOrders.Insert(risesAbove)
  1394  		if risesAbove.Expiry.Expires() {
  1395  			m.expiringStopOrders.Insert(risesAbove.ID, risesAbove.Expiry.ExpiresAt.UnixNano())
  1396  		}
  1397  	}
  1398  }
  1399  
  1400  func (m *Market) stopOrderWouldTriggerAtSubmission(
  1401  	stopOrder *types.StopOrder,
  1402  ) bool {
  1403  	if m.lastTradedPrice == nil || stopOrder == nil || stopOrder.Trigger.IsTrailingPercentOffset() {
  1404  		return false
  1405  	}
  1406  
  1407  	lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice())
  1408  
  1409  	switch stopOrder.Trigger.Direction {
  1410  	case types.StopOrderTriggerDirectionFallsBelow:
  1411  		if lastTradedPrice.LTE(stopOrder.Trigger.Price()) {
  1412  			return true
  1413  		}
  1414  	case types.StopOrderTriggerDirectionRisesAbove:
  1415  		if lastTradedPrice.GTE(stopOrder.Trigger.Price()) {
  1416  			return true
  1417  		}
  1418  	}
  1419  	return false
  1420  }
  1421  
  1422  func (m *Market) triggerStopOrders(
  1423  	ctx context.Context,
  1424  	idgen common.IDGenerator,
  1425  ) []*types.OrderConfirmation {
  1426  	if m.lastTradedPrice == nil {
  1427  		return nil
  1428  	}
  1429  	lastTradedPrice := m.priceToMarketPrecision(m.getLastTradedPrice())
  1430  	triggered, cancelled := m.stopOrders.PriceUpdated(lastTradedPrice)
  1431  
  1432  	if len(triggered) <= 0 && len(cancelled) <= 0 {
  1433  		return nil
  1434  	}
  1435  
  1436  	now := m.timeService.GetTimeNow()
  1437  	// remove from expiring orders + set updatedAt
  1438  	for _, v := range append(triggered, cancelled...) {
  1439  		v.UpdatedAt = now
  1440  		if v.Expiry.Expires() {
  1441  			m.expiringStopOrders.RemoveOrder(v.Expiry.ExpiresAt.UnixNano(), v.ID)
  1442  		}
  1443  	}
  1444  	evts := make([]events.Event, 0, len(cancelled))
  1445  	for _, v := range cancelled {
  1446  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  1447  	}
  1448  
  1449  	m.broker.SendBatch(evts)
  1450  
  1451  	if len(triggered) <= 0 {
  1452  		return nil
  1453  	}
  1454  
  1455  	confirmations := m.submitStopOrders(ctx, triggered, types.StopOrderStatusTriggered, idgen)
  1456  
  1457  	return append(m.triggerStopOrders(ctx, idgen), confirmations...)
  1458  }
  1459  
  1460  // SubmitOrder submits the given order.
  1461  func (m *Market) SubmitOrder(ctx context.Context, orderSubmission *types.OrderSubmission, party string, deterministicID string) (oc *types.OrderConfirmation, _ error) {
  1462  	idgen := idgeneration.New(deterministicID)
  1463  	return m.SubmitOrderWithIDGeneratorAndOrderID(ctx, orderSubmission, party, idgen, idgen.NextID(), true)
  1464  }
  1465  
  1466  // SubmitOrderWithIDGeneratorAndOrderID submits the given order.
  1467  func (m *Market) SubmitOrderWithIDGeneratorAndOrderID(ctx context.Context, orderSubmission *types.OrderSubmission, party string, idgen common.IDGenerator, orderID string, checkForTriggers bool) (oc *types.OrderConfirmation, _ error) {
  1468  	m.idgen = idgen
  1469  	defer func() { m.idgen = nil }()
  1470  
  1471  	defer func() {
  1472  		if !checkForTriggers {
  1473  			return
  1474  		}
  1475  
  1476  		m.triggerStopOrders(ctx, idgen)
  1477  	}()
  1478  
  1479  	order := orderSubmission.IntoOrder(party)
  1480  	if order.Price != nil {
  1481  		order.OriginalPrice = order.Price.Clone()
  1482  		order.Price, _ = num.UintFromDecimal(order.Price.ToDecimal().Mul(m.priceFactor))
  1483  	}
  1484  	order.CreatedAt = m.timeService.GetTimeNow().UnixNano()
  1485  	order.ID = orderID
  1486  
  1487  	if !m.canTrade() {
  1488  		order.Status = types.OrderStatusRejected
  1489  		order.Reason = types.OrderErrorMarketClosed
  1490  		m.broker.Send(events.NewOrderEvent(ctx, order))
  1491  		return nil, common.ErrTradingNotAllowed
  1492  	}
  1493  
  1494  	if !m.canSubmitMaybeSell(order.Party, order.Side) {
  1495  		order.Status = types.OrderStatusRejected
  1496  		order.Reason = types.OrderErrorSellOrderNotAllowed
  1497  		m.broker.Send(events.NewOrderEvent(ctx, order))
  1498  		return nil, common.ErrSellOrderNotAllowed
  1499  	}
  1500  
  1501  	conf, _, err := m.submitOrder(ctx, order)
  1502  	if err != nil {
  1503  		return nil, err
  1504  	}
  1505  
  1506  	if !m.as.InAuction() {
  1507  		m.checkForReferenceMoves(ctx, false)
  1508  	}
  1509  	return conf, nil
  1510  }
  1511  
  1512  // submitOrder validates and submits an order.
  1513  func (m *Market) submitOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) {
  1514  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "SubmitOrder")
  1515  	orderValidity := "invalid"
  1516  	defer func() {
  1517  		timer.EngineTimeCounterAdd()
  1518  		metrics.OrderCounterInc(m.mkt.ID, orderValidity)
  1519  	}()
  1520  
  1521  	// set those at the beginning as even rejected order get through the buffers
  1522  	order.Version = common.InitialOrderVersion
  1523  	order.Status = types.OrderStatusActive
  1524  
  1525  	if err := m.validateOrder(ctx, order); err != nil {
  1526  		return nil, nil, err
  1527  	}
  1528  
  1529  	if err := m.validateAccounts(ctx, order); err != nil {
  1530  		return nil, nil, err
  1531  	}
  1532  
  1533  	// Now that validation is handled, call the code to place the order
  1534  	orderConf, orderUpdates, err := m.submitValidatedOrder(ctx, order)
  1535  	if err == nil {
  1536  		orderValidity = "valid"
  1537  	}
  1538  
  1539  	if order.PeggedOrder != nil && order.IsFinished() {
  1540  		// remove the pegged order from anywhere
  1541  		m.removePeggedOrder(order)
  1542  	}
  1543  
  1544  	// insert an expiring order if it's either in the book
  1545  	// or in the parked list
  1546  	if order.IsExpireable() && !order.IsFinished() {
  1547  		m.expiringOrders.Insert(order.ID, order.ExpiresAt)
  1548  	}
  1549  
  1550  	return orderConf, orderUpdates, err
  1551  }
  1552  
  1553  func (m *Market) canCoverTradesAndFees(party string, partySide types.Side, trades []*types.Trade) error {
  1554  	// check that the party can afford the traded amount + fees
  1555  	if partySide == types.SideBuy {
  1556  		totalTraded := num.UintZero()
  1557  		for _, t := range trades {
  1558  			fees, err := m.calculateFeesForTrades([]*types.Trade{t})
  1559  			if err != nil {
  1560  				m.log.Panic("failed to calculate fees for trade", logging.Trade(t))
  1561  			}
  1562  			size := num.NewUint(t.Size)
  1563  			totalTraded.AddSum(size.Mul(size, t.Price), fees.TotalFeesAmountPerParty()[party])
  1564  		}
  1565  		totalTraded, _ = num.UintFromDecimal(totalTraded.ToDecimal().Div(m.positionFactor))
  1566  		if err := m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, totalTraded, types.AccountTypeGeneral); err != nil {
  1567  			return err
  1568  		}
  1569  	} else {
  1570  		sizeTraded := uint64(0)
  1571  		for _, t := range trades {
  1572  			sizeTraded += t.Size
  1573  		}
  1574  		totalTraded := scaleBaseQuantityToAssetDP(sizeTraded, m.baseFactor)
  1575  		if err := m.collateral.PartyHasSufficientBalance(m.baseAsset, party, totalTraded, types.AccountTypeGeneral); err != nil {
  1576  			return err
  1577  		}
  1578  	}
  1579  	return nil
  1580  }
  1581  
  1582  // submitValidatedOrder submits a new order.
  1583  func (m *Market) submitValidatedOrder(ctx context.Context, order *types.Order) (*types.OrderConfirmation, []*types.Order, error) {
  1584  	isPegged := order.PeggedOrder != nil
  1585  	if isPegged {
  1586  		order.Status = types.OrderStatusParked
  1587  		order.Reason = types.OrderErrorUnspecified
  1588  
  1589  		if m.as.InAuction() {
  1590  			order.SetIcebergPeaks()
  1591  			// as the order can't trade we don't transfer from the general account to the holding account in this case.
  1592  			m.peggedOrders.Park(order)
  1593  			// If we are in an auction, we don't insert this order into the book
  1594  			// Maybe should return an orderConfirmation with order state PARKED
  1595  			m.broker.Send(events.NewOrderEvent(ctx, order))
  1596  			return &types.OrderConfirmation{Order: order}, nil, nil
  1597  		}
  1598  		err := m.repricePeggedOrder(order)
  1599  		if err != nil {
  1600  			order.SetIcebergPeaks()
  1601  			m.peggedOrders.Park(order)
  1602  			m.broker.Send(events.NewOrderEvent(ctx, order))
  1603  			return &types.OrderConfirmation{Order: order}, nil, nil // nolint
  1604  		}
  1605  	}
  1606  	var trades []*types.Trade
  1607  	// we're not in auction (not opening, not any other auction
  1608  	if !m.as.InAuction() {
  1609  		// first we call the order book to evaluate auction triggers and get the list of trades
  1610  		var err error
  1611  		trades, err = m.checkPriceAndGetTrades(ctx, order)
  1612  		if err != nil {
  1613  			return nil, nil, m.unregisterAndReject(ctx, order, err)
  1614  		}
  1615  		// NB we don't apply fees here because if this is a sell the fees are taken from the quantity that the buyer pays (in quote asset)
  1616  		// so this is deferred to handling confirmations - by this point the aggressor must have sufficient funds to cover for fees so this should
  1617  		// not be an issue
  1618  	}
  1619  
  1620  	// check that the party can afford the trade and fees
  1621  	if trades != nil {
  1622  		if err := m.canCoverTradesAndFees(order.Party, order.Side, trades); err != nil {
  1623  			return nil, nil, m.unregisterAndReject(ctx, order, err)
  1624  		}
  1625  	}
  1626  
  1627  	// if an auction is ongoing and the order is pegged, park it and return
  1628  	if m.as.InAuction() && isPegged {
  1629  		if isPegged {
  1630  			m.peggedOrders.Park(order)
  1631  		}
  1632  		m.broker.Send(events.NewOrderEvent(ctx, order))
  1633  		return &types.OrderConfirmation{Order: order}, nil, nil
  1634  	}
  1635  
  1636  	order.Status = types.OrderStatusActive
  1637  
  1638  	// Send the aggressive order into matching engine
  1639  	confirmation, err := m.matching.SubmitOrder(order)
  1640  	if err != nil {
  1641  		return nil, nil, m.unregisterAndReject(ctx, order, err)
  1642  	}
  1643  
  1644  	// if the order is not finished and remaining is non zero, we need to transfer the remaining base/quote from the general account
  1645  	// to the holding account for the market/asset. If an auction is on-going we also need to account for potential fees (applicable for buy orders only)
  1646  	if !order.IsFinished() && order.Remaining > 0 {
  1647  		err := m.transferToHoldingAccount(ctx, order)
  1648  		if err != nil {
  1649  			return nil, nil, m.unregisterAndReject(ctx, order, err)
  1650  		}
  1651  	}
  1652  
  1653  	// we replace the trades in the confirmation with the one we got initially
  1654  	// the contains the fees information
  1655  	// NB: I have to say this this is a weird way of doing it, why are we doing it twice?
  1656  	confirmation.Trades = trades
  1657  
  1658  	// Send out the order update here as handling the confirmation message
  1659  	// below might trigger an action that can change the order details.
  1660  	m.broker.Send(events.NewOrderEvent(ctx, order))
  1661  
  1662  	orderUpdates := m.handleConfirmation(ctx, confirmation)
  1663  	m.handleConfirmationPassiveOrders(ctx, confirmation)
  1664  	return confirmation, orderUpdates, nil
  1665  }
  1666  
  1667  // checkPriceAndGetTrades calculates the trades that would be generated from the given order.
  1668  func (m *Market) checkPriceAndGetTrades(ctx context.Context, order *types.Order) ([]*types.Trade, error) {
  1669  	trades, err := m.matching.GetTrades(order)
  1670  	if err != nil {
  1671  		return nil, err
  1672  	}
  1673  
  1674  	if order.PostOnly && len(trades) > 0 {
  1675  		return nil, types.OrderErrorPostOnlyOrderWouldTrade
  1676  	}
  1677  
  1678  	persistent := true
  1679  	switch order.TimeInForce {
  1680  	case types.OrderTimeInForceFOK, types.OrderTimeInForceGFN, types.OrderTimeInForceIOC:
  1681  		persistent = false
  1682  	}
  1683  
  1684  	for _, trade := range trades {
  1685  		if m.pMonitor.CheckPrice(ctx, m.as, trade.Price, persistent, false) {
  1686  			return nil, types.OrderErrorNonPersistentOrderOutOfPriceBounds
  1687  		}
  1688  
  1689  		if evt := m.as.AuctionExtended(ctx, m.timeService.GetTimeNow()); evt != nil {
  1690  			m.broker.Send(evt)
  1691  		}
  1692  
  1693  		// start the  monitoring auction if required?
  1694  		if m.as.AuctionStart() {
  1695  			m.enterAuction(ctx)
  1696  			return nil, nil
  1697  		}
  1698  	}
  1699  
  1700  	return trades, nil
  1701  }
  1702  
  1703  // addParty adds the party to the market mapping.
  1704  func (m *Market) addParty(party string) {
  1705  	if _, ok := m.parties[party]; !ok {
  1706  		m.parties[party] = struct{}{}
  1707  	}
  1708  }
  1709  
  1710  // applyFees handles transfer of fee payment from the *buyer* to the fees account.
  1711  func (m *Market) applyFees(ctx context.Context, fees events.FeesTransfer, sourceAccountType types.AccountType) error {
  1712  	var (
  1713  		transfers []*types.LedgerMovement
  1714  		err       error
  1715  	)
  1716  
  1717  	if !m.as.InAuction() {
  1718  		transfers, err = m.collateral.TransferSpotFeesContinuousTrading(ctx, m.GetID(), m.quoteAsset, fees)
  1719  	} else if m.as.IsMonitorAuction() || m.as.IsPAPAuction() {
  1720  		transfers, err = m.collateral.TransferSpotFees(ctx, m.GetID(), m.quoteAsset, fees, sourceAccountType)
  1721  	} else if m.as.IsFBA() {
  1722  		transfers, err = m.collateral.TransferSpotFees(ctx, m.GetID(), m.quoteAsset, fees, sourceAccountType)
  1723  	}
  1724  
  1725  	if len(transfers) > 0 {
  1726  		m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  1727  	}
  1728  
  1729  	m.marketActivityTracker.UpdateFeesFromTransfers(m.quoteAsset, m.GetID(), fees.Transfers())
  1730  	return err
  1731  }
  1732  
  1733  func (m *Market) handleConfirmationPassiveOrders(ctx context.Context, conf *types.OrderConfirmation) {
  1734  	le := []*types.LedgerMovement{}
  1735  
  1736  	if conf.PassiveOrdersAffected != nil {
  1737  		evts := make([]events.Event, 0, len(conf.PassiveOrdersAffected))
  1738  
  1739  		// Insert or update passive orders siting on the book
  1740  		for _, order := range conf.PassiveOrdersAffected {
  1741  			// set the `updatedAt` value as these orders have changed
  1742  			order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
  1743  			evts = append(evts, events.NewOrderEvent(ctx, order))
  1744  
  1745  			// If the order is a pegged order and is complete we must remove it from the pegged list
  1746  			if order.PeggedOrder != nil {
  1747  				if order.Remaining == 0 || order.Status != types.OrderStatusActive {
  1748  					m.removePeggedOrder(order)
  1749  				}
  1750  			}
  1751  
  1752  			if order.IsFinished() {
  1753  				m.releaseOrderFromHoldingAccount(ctx, order.ID, order.Party, order.Side)
  1754  			}
  1755  
  1756  			// remove the order from the expiring list
  1757  			// if it was a GTT order
  1758  			if order.IsExpireable() && order.IsFinished() {
  1759  				m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID)
  1760  			}
  1761  		}
  1762  		if len(le) > 0 {
  1763  			m.broker.Send(events.NewLedgerMovements(ctx, le))
  1764  		}
  1765  		m.broker.SendBatch(evts)
  1766  	}
  1767  }
  1768  
  1769  func (m *Market) handleConfirmation(ctx context.Context, conf *types.OrderConfirmation) []*types.Order {
  1770  	// When re-submitting liquidity order, it happen that the pricing is putting
  1771  	// the order at a price which makes it uncross straight away.
  1772  	// then triggering this handleConfirmation flow, etc.
  1773  	// Although the order is considered aggressive, and we never expect in the flow
  1774  	// for an aggressive order to be pegged, so we never remove them from the pegged
  1775  	// list. All this impact the float of EnterAuction, which if triggered from there
  1776  	// will try to park all pegged orders, including this order which have never been
  1777  	// removed from the pegged list. We add this check to make sure  that if the
  1778  	// aggressive order is pegged, we then do remove it from the list.
  1779  	if conf.Order.PeggedOrder != nil {
  1780  		if conf.Order.Remaining == 0 || conf.Order.Status != types.OrderStatusActive {
  1781  			m.removePeggedOrder(conf.Order)
  1782  		}
  1783  	}
  1784  
  1785  	end := m.as.CanLeave()
  1786  	orderUpdates := make([]*types.Order, 0, len(conf.PassiveOrdersAffected)+1)
  1787  	orderUpdates = append(orderUpdates, conf.Order)
  1788  	orderUpdates = append(orderUpdates, conf.PassiveOrdersAffected...)
  1789  
  1790  	if len(conf.Trades) == 0 {
  1791  		return orderUpdates
  1792  	}
  1793  	m.setLastTradedPrice(conf.Trades[len(conf.Trades)-1])
  1794  	m.hasTraded = true
  1795  
  1796  	// Insert all trades resulted from the executed order
  1797  	tradeEvts := make([]events.Event, 0, len(conf.Trades))
  1798  	tradedValue, _ := num.UintFromDecimal(
  1799  		conf.TradedValue().ToDecimal().Div(m.positionFactor))
  1800  
  1801  	transfers := []*types.LedgerMovement{}
  1802  	for idx, trade := range conf.Trades {
  1803  		trade.SetIDs(m.idgen.NextID(), conf.Order, conf.PassiveOrdersAffected[idx])
  1804  		notionalTraded, _ := num.UintFromDecimal(num.UintZero().Mul(num.NewUint(trade.Size), trade.Price).ToDecimal().Div(m.positionFactor))
  1805  		m.marketActivityTracker.RecordNotionalTraded(m.quoteAsset, m.mkt.ID, notionalTraded)
  1806  		tradeTransfers := m.handleTrade(ctx, trade)
  1807  		transfers = append(transfers, tradeTransfers...)
  1808  		tradeEvts = append(tradeEvts, events.NewTradeEvent(ctx, *trade))
  1809  	}
  1810  	if conf.Order.IsFinished() {
  1811  		m.releaseOrderFromHoldingAccount(ctx, conf.Order.ID, conf.Order.Party, conf.Order.Side)
  1812  	}
  1813  
  1814  	if len(transfers) > 0 {
  1815  		m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  1816  	}
  1817  
  1818  	if !m.as.InAuction() {
  1819  		aggressor := conf.Order.Party
  1820  		if quantum, err := m.collateral.GetAssetQuantum(m.quoteAsset); err == nil && !quantum.IsZero() {
  1821  			n, _ := num.UintFromDecimal(tradedValue.ToDecimal().Div(quantum))
  1822  			m.marketActivityTracker.RecordNotionalTakerVolume(m.mkt.ID, aggressor, n)
  1823  		}
  1824  	}
  1825  
  1826  	m.feeSplitter.AddTradeValue(tradedValue)
  1827  	m.marketActivityTracker.AddValueTraded(m.quoteAsset, m.mkt.ID, tradedValue)
  1828  	m.broker.SendBatch(tradeEvts)
  1829  	// check reference moves if we have order updates, and we are not in an auction (or leaving an auction)
  1830  	// we handle reference moves in confirmMTM when leaving an auction already
  1831  	if len(orderUpdates) > 0 && !end && !m.as.InAuction() {
  1832  		m.checkForReferenceMoves(
  1833  			ctx, false)
  1834  	}
  1835  
  1836  	return orderUpdates
  1837  }
  1838  
  1839  // updateLiquidityFee computes the current LiquidityProvision fee and updates
  1840  // the fee engine.
  1841  func (m *Market) updateLiquidityFee(ctx context.Context) {
  1842  	var fee num.Decimal
  1843  	switch m.mkt.Fees.LiquidityFeeSettings.Method {
  1844  	case types.LiquidityFeeMethodConstant:
  1845  		fee = m.mkt.Fees.LiquidityFeeSettings.FeeConstant
  1846  	case types.LiquidityFeeMethodMarginalCost:
  1847  		fee = m.liquidityEngine.ProvisionsPerParty().FeeForTarget(m.getTargetStake())
  1848  	case types.LiquidityFeeMethodWeightedAverage:
  1849  		fee = m.liquidityEngine.ProvisionsPerParty().FeeForWeightedAverage()
  1850  	default:
  1851  		m.log.Panic("unknown liquidity fee method")
  1852  	}
  1853  
  1854  	if !fee.Equals(m.getLiquidityFee()) {
  1855  		m.fee.SetLiquidityFee(fee)
  1856  		m.setLiquidityFee(fee)
  1857  		m.broker.Send(
  1858  			events.NewMarketUpdatedEvent(ctx, *m.mkt),
  1859  		)
  1860  	}
  1861  }
  1862  
  1863  func (m *Market) setLiquidityFee(fee num.Decimal) {
  1864  	m.mkt.Fees.Factors.LiquidityFee = fee
  1865  }
  1866  
  1867  func (m *Market) getLiquidityFee() num.Decimal {
  1868  	return m.mkt.Fees.Factors.LiquidityFee
  1869  }
  1870  
  1871  func (m *Market) setLastTradedPrice(trade *types.Trade) {
  1872  	m.lastTradedPrice = trade.Price.Clone()
  1873  }
  1874  
  1875  func (m *Market) CancelAllStopOrders(ctx context.Context, partyID string) error {
  1876  	if !m.canTrade() {
  1877  		return common.ErrTradingNotAllowed
  1878  	}
  1879  
  1880  	stopOrders, err := m.stopOrders.Cancel(partyID, "")
  1881  	if err != nil {
  1882  		return err
  1883  	}
  1884  
  1885  	m.removeCancelledExpiringStopOrders(stopOrders)
  1886  
  1887  	evts := make([]events.Event, 0, len(stopOrders))
  1888  	for _, v := range stopOrders {
  1889  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  1890  	}
  1891  
  1892  	m.broker.SendBatch(evts)
  1893  
  1894  	return nil
  1895  }
  1896  
  1897  // CancelAllOrders cancels all orders in the market.
  1898  func (m *Market) CancelAllOrders(ctx context.Context, partyID string) ([]*types.OrderCancellationConfirmation, error) {
  1899  	if !m.canTrade() {
  1900  		return nil, common.ErrTradingNotAllowed
  1901  	}
  1902  
  1903  	// get all order for this party in the book
  1904  	orders := m.matching.GetOrdersPerParty(partyID)
  1905  
  1906  	// add all orders being eventually parked
  1907  	orders = append(orders, m.peggedOrders.GetAllParkedForParty(partyID)...)
  1908  
  1909  	// just an early exit, there's just no orders...
  1910  	if len(orders) <= 0 {
  1911  		return nil, nil
  1912  	}
  1913  
  1914  	// now we eventually dedup them
  1915  	uniq := map[string]*types.Order{}
  1916  	for _, v := range orders {
  1917  		uniq[v.ID] = v
  1918  	}
  1919  
  1920  	// put them back in the slice, and sort them
  1921  	orders = make([]*types.Order, 0, len(uniq))
  1922  	for _, v := range uniq {
  1923  		orders = append(orders, v)
  1924  	}
  1925  	sort.Slice(orders, func(i, j int) bool {
  1926  		return orders[i].ID < orders[j].ID
  1927  	})
  1928  
  1929  	cancellations := make([]*types.OrderCancellationConfirmation, 0, len(orders))
  1930  	orderIDs := make([]string, 0, len(orders))
  1931  
  1932  	// now iterate over all orders and cancel one by one.
  1933  	for _, order := range orders {
  1934  		cancellation, err := m.cancelOrderInBatch(ctx, partyID, order.ID)
  1935  		if err != nil {
  1936  			return nil, err
  1937  		}
  1938  		cancellations = append(cancellations, cancellation)
  1939  		orderIDs = append(orderIDs, order.ID)
  1940  	}
  1941  
  1942  	m.broker.Send(events.NewCancelledOrdersEvent(ctx, m.GetID(), partyID, orderIDs...))
  1943  
  1944  	m.checkForReferenceMoves(ctx, false)
  1945  
  1946  	return cancellations, nil
  1947  }
  1948  
  1949  func (m *Market) CancelStopOrder(
  1950  	ctx context.Context,
  1951  	partyID, orderID string,
  1952  ) error {
  1953  	if !m.canTrade() {
  1954  		return common.ErrTradingNotAllowed
  1955  	}
  1956  
  1957  	stopOrders, err := m.stopOrders.Cancel(partyID, orderID)
  1958  	if err != nil || len(stopOrders) <= 0 { // could return just an empty slice
  1959  		return err
  1960  	}
  1961  
  1962  	m.removeCancelledExpiringStopOrders(stopOrders)
  1963  
  1964  	evts := []events.Event{events.NewStopOrderEvent(ctx, stopOrders[0])}
  1965  	if len(stopOrders) > 1 {
  1966  		evts = append(evts, events.NewStopOrderEvent(ctx, stopOrders[1]))
  1967  	}
  1968  
  1969  	m.broker.SendBatch(evts)
  1970  
  1971  	return nil
  1972  }
  1973  
  1974  func (m *Market) removeCancelledExpiringStopOrders(
  1975  	stopOrders []*types.StopOrder,
  1976  ) {
  1977  	for _, o := range stopOrders {
  1978  		if o.Expiry.Expires() {
  1979  			m.expiringStopOrders.RemoveOrder(o.Expiry.ExpiresAt.UnixNano(), o.ID)
  1980  		}
  1981  	}
  1982  }
  1983  
  1984  // CancelOrder canels a single order in the market.
  1985  func (m *Market) CancelOrder(ctx context.Context, partyID, orderID string, deterministicID string) (oc *types.OrderCancellationConfirmation, _ error) {
  1986  	idgen := idgeneration.New(deterministicID)
  1987  	return m.CancelOrderWithIDGenerator(ctx, partyID, orderID, idgen)
  1988  }
  1989  
  1990  // CancelOrderWithIDGenerator cancels an order in the market.
  1991  func (m *Market) CancelOrderWithIDGenerator(ctx context.Context, partyID, orderID string, idgen common.IDGenerator) (oc *types.OrderCancellationConfirmation, _ error) {
  1992  	m.idgen = idgen
  1993  	defer func() { m.idgen = nil }()
  1994  
  1995  	if !m.canTrade() {
  1996  		return nil, common.ErrTradingNotAllowed
  1997  	}
  1998  
  1999  	conf, err := m.cancelOrder(ctx, partyID, orderID)
  2000  	if err != nil {
  2001  		return conf, err
  2002  	}
  2003  
  2004  	if !m.as.InAuction() {
  2005  		m.checkForReferenceMoves(ctx, false)
  2006  	}
  2007  
  2008  	return conf, nil
  2009  }
  2010  
  2011  func (m *Market) cancelOrderInBatch(ctx context.Context, partyID, orderID string) (*types.OrderCancellationConfirmation, error) {
  2012  	return m.cancelSingleOrder(ctx, partyID, orderID, true)
  2013  }
  2014  
  2015  // cancelOrder cancels the given order. If the order is found on the book, we release locked funds from holding account to the general account of the party.
  2016  func (m *Market) cancelOrder(ctx context.Context, partyID, orderID string) (*types.OrderCancellationConfirmation, error) {
  2017  	return m.cancelSingleOrder(ctx, partyID, orderID, false)
  2018  }
  2019  
  2020  // cancelSingleOrder cancels the given order. If the order is found on the book, we release locked funds from holding account to the general account of the party.
  2021  func (m *Market) cancelSingleOrder(ctx context.Context, partyID, orderID string, inBatch bool) (*types.OrderCancellationConfirmation, error) {
  2022  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "CancelOrder")
  2023  	defer timer.EngineTimeCounterAdd()
  2024  
  2025  	if m.closed {
  2026  		return nil, common.ErrMarketClosed
  2027  	}
  2028  
  2029  	order, foundOnBook, err := m.getOrderByID(orderID)
  2030  	if err != nil {
  2031  		return nil, err
  2032  	}
  2033  
  2034  	// Only allow the original order creator to cancel their order
  2035  	if order.Party != partyID {
  2036  		if m.log.GetLevel() == logging.DebugLevel {
  2037  			m.log.Debug("Party ID mismatch",
  2038  				logging.String("party-id", partyID),
  2039  				logging.String("order-id", orderID),
  2040  				logging.String("market", m.mkt.ID))
  2041  		}
  2042  		return nil, types.ErrInvalidPartyID
  2043  	}
  2044  
  2045  	if foundOnBook {
  2046  		cancellation, err := m.matching.CancelOrder(order)
  2047  		if cancellation == nil || err != nil {
  2048  			if m.log.GetLevel() == logging.DebugLevel {
  2049  				m.log.Debug("Failure after cancel order from matching engine",
  2050  					logging.String("party-id", partyID),
  2051  					logging.String("order-id", orderID),
  2052  					logging.String("market", m.mkt.ID),
  2053  					logging.Error(err))
  2054  			}
  2055  			return nil, err
  2056  		}
  2057  		m.releaseOrderFromHoldingAccount(ctx, orderID, order.Party, order.Side)
  2058  	}
  2059  
  2060  	if order.IsExpireable() {
  2061  		m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID)
  2062  	}
  2063  
  2064  	// If this is a pegged order, remove from pegged and parked lists
  2065  	if order.PeggedOrder != nil {
  2066  		m.removePeggedOrder(order)
  2067  		order.Status = types.OrderStatusCancelled
  2068  	}
  2069  
  2070  	// Publish the changed order details
  2071  	order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
  2072  	if !inBatch {
  2073  		m.broker.Send(events.NewCancelledOrdersEvent(ctx, m.GetID(), partyID, orderID))
  2074  	}
  2075  
  2076  	return &types.OrderCancellationConfirmation{Order: order}, nil
  2077  }
  2078  
  2079  // parkOrder removes the given order from the orderbook. parkOrder will panic if it encounters errors, which means that it reached an
  2080  // invalid state. When the order is parked, the funds from the holding account are released to the general account.
  2081  func (m *Market) parkOrder(ctx context.Context, orderID string) *types.Order {
  2082  	order, err := m.matching.RemoveOrder(orderID)
  2083  	if err != nil {
  2084  		m.log.Panic("Failure to remove order from matching engine",
  2085  			logging.OrderID(orderID),
  2086  			logging.Error(err))
  2087  	}
  2088  	m.releaseOrderFromHoldingAccount(ctx, orderID, order.Party, order.Side)
  2089  	m.peggedOrders.Park(order)
  2090  	m.broker.Send(events.NewOrderEvent(ctx, order))
  2091  	return order
  2092  }
  2093  
  2094  // AmendOrder amend an existing order from the order book.
  2095  func (m *Market) AmendOrder(ctx context.Context, orderAmendment *types.OrderAmendment, party string, deterministicID string) (oc *types.OrderConfirmation, _ error) {
  2096  	idgen := idgeneration.New(deterministicID)
  2097  	return m.AmendOrderWithIDGenerator(ctx, orderAmendment, party, idgen)
  2098  }
  2099  
  2100  // AmendOrderWithIDGenerator amends an order.
  2101  func (m *Market) AmendOrderWithIDGenerator(ctx context.Context, orderAmendment *types.OrderAmendment, party string, idgen common.IDGenerator) (oc *types.OrderConfirmation, _ error) {
  2102  	m.idgen = idgen
  2103  	defer func() { m.idgen = nil }()
  2104  
  2105  	defer func() {
  2106  		m.triggerStopOrders(ctx, idgen)
  2107  	}()
  2108  
  2109  	if !m.canTrade() {
  2110  		return nil, common.ErrTradingNotAllowed
  2111  	}
  2112  
  2113  	conf, _, err := m.amendOrder(ctx, orderAmendment, party)
  2114  	if err != nil {
  2115  		return nil, err
  2116  	}
  2117  
  2118  	if !m.as.InAuction() {
  2119  		m.checkForReferenceMoves(ctx, false)
  2120  	}
  2121  	return conf, nil
  2122  }
  2123  
  2124  // findOrderAndEnsureOwnership checks that the party is actually the owner of the order ID.
  2125  func (m *Market) findOrderAndEnsureOwnership(orderID, partyID, marketID string) (exitingOrder *types.Order, foundOnBook bool, err error) {
  2126  	// Try and locate the existing order specified on the
  2127  	// order book in the matching engine for this market
  2128  	existingOrder, foundOnBook, err := m.getOrderByID(orderID)
  2129  	if err != nil {
  2130  		if m.log.GetLevel() == logging.DebugLevel {
  2131  			m.log.Debug("Invalid order ID",
  2132  				logging.OrderID(orderID),
  2133  				logging.PartyID(partyID),
  2134  				logging.MarketID(marketID),
  2135  				logging.Error(err))
  2136  		}
  2137  		return nil, false, types.ErrInvalidOrderID
  2138  	}
  2139  
  2140  	// We can only amend this order if we created it
  2141  	if existingOrder.Party != partyID {
  2142  		if m.log.GetLevel() == logging.DebugLevel {
  2143  			m.log.Debug("Invalid party ID",
  2144  				logging.String("original party id:", existingOrder.Party),
  2145  				logging.PartyID(partyID),
  2146  			)
  2147  		}
  2148  		return nil, false, types.ErrInvalidPartyID
  2149  	}
  2150  
  2151  	// Validate Market
  2152  	if existingOrder.MarketID != marketID {
  2153  		// we should never reach this point
  2154  		m.log.Panic("Market ID mismatch",
  2155  			logging.MarketID(m.mkt.ID),
  2156  			logging.Order(*existingOrder),
  2157  			logging.Error(types.ErrInvalidMarketID),
  2158  		)
  2159  	}
  2160  
  2161  	return existingOrder, foundOnBook, err
  2162  }
  2163  
  2164  func (m *Market) amendOrder(ctx context.Context, orderAmendment *types.OrderAmendment, party string) (cnf *types.OrderConfirmation, orderUpdates []*types.Order, returnedErr error) {
  2165  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "AmendOrder")
  2166  	defer timer.EngineTimeCounterAdd()
  2167  
  2168  	// Verify that the market is not closed
  2169  	if m.closed {
  2170  		return nil, nil, common.ErrMarketClosed
  2171  	}
  2172  
  2173  	existingOrder, foundOnBook, err := m.findOrderAndEnsureOwnership(orderAmendment.OrderID, party, m.GetID())
  2174  	if err != nil {
  2175  		return nil, nil, err
  2176  	}
  2177  
  2178  	if err := m.validateOrderAmendment(existingOrder, orderAmendment); err != nil {
  2179  		return nil, nil, err
  2180  	}
  2181  
  2182  	amendedOrder, err := existingOrder.ApplyOrderAmendment(orderAmendment, m.timeService.GetTimeNow().UnixNano(), m.priceFactor)
  2183  	if err != nil {
  2184  		return nil, nil, err
  2185  	}
  2186  
  2187  	if err := m.checkOrderAmendForSpam(amendedOrder); err != nil {
  2188  		return nil, nil, err
  2189  	}
  2190  
  2191  	if orderAmendment.Price != nil && amendedOrder.OriginalPrice != nil {
  2192  		if err = m.validateTickSize(amendedOrder.OriginalPrice); err != nil {
  2193  			return nil, nil, err
  2194  		}
  2195  	}
  2196  
  2197  	// We do this first, just in case the party would also have
  2198  	// change the expiry, and that would have been caught by
  2199  	// the follow up checks, so we do not insert a non-existing
  2200  	// order in the expiring orders
  2201  	// if remaining is reduces <= 0, then order is cancelled
  2202  	if amendedOrder.Remaining <= 0 {
  2203  		confirm, err := m.cancelOrder(
  2204  			ctx, existingOrder.Party, existingOrder.ID)
  2205  		if err != nil {
  2206  			return nil, nil, err
  2207  		}
  2208  		return &types.OrderConfirmation{
  2209  			Order: confirm.Order,
  2210  		}, nil, nil
  2211  	}
  2212  
  2213  	// If we have a pegged order that is no longer expiring, we need to remove it
  2214  	var (
  2215  		needToRemoveExpiry, needToAddExpiry bool
  2216  		expiresAt                           int64
  2217  	)
  2218  
  2219  	defer func() {
  2220  		// no errors, amend most likely happened properly
  2221  		if returnedErr == nil {
  2222  			if needToRemoveExpiry {
  2223  				m.expiringOrders.RemoveOrder(expiresAt, existingOrder.ID)
  2224  			}
  2225  			// need to make sure the order haven't been matched with the
  2226  			// amend, consuming the remain volume as well or we would
  2227  			// add an order while it's not needed to the expiring list
  2228  			if needToAddExpiry && cnf != nil && !cnf.Order.IsFinished() {
  2229  				m.expiringOrders.Insert(amendedOrder.ID, amendedOrder.ExpiresAt)
  2230  			}
  2231  		}
  2232  	}()
  2233  
  2234  	// if we are amending from GTT to GTC, flag ready to remove from expiry list
  2235  	if existingOrder.IsExpireable() && !amendedOrder.IsExpireable() {
  2236  		// We no longer need to handle the expiry
  2237  		needToRemoveExpiry = true
  2238  		expiresAt = existingOrder.ExpiresAt
  2239  	}
  2240  
  2241  	// if we are amending from GTC to GTT, flag ready to add to expiry list
  2242  	if !existingOrder.IsExpireable() && amendedOrder.IsExpireable() {
  2243  		// We need to handle the expiry
  2244  		needToAddExpiry = true
  2245  	}
  2246  
  2247  	// if both where expireable but we changed the duration
  2248  	// then we need to remove, then reinsert...
  2249  	if existingOrder.IsExpireable() && amendedOrder.IsExpireable() &&
  2250  		existingOrder.ExpiresAt != amendedOrder.ExpiresAt {
  2251  		// Still expiring but needs to be updated in the expiring
  2252  		// orders pool
  2253  		needToRemoveExpiry = true
  2254  		needToAddExpiry = true
  2255  		expiresAt = existingOrder.ExpiresAt
  2256  	}
  2257  
  2258  	// if expiration has changed and is before the original creation time, reject this amend
  2259  	if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < existingOrder.CreatedAt {
  2260  		if m.log.GetLevel() == logging.DebugLevel {
  2261  			m.log.Debug("Amended expiry before original creation time",
  2262  				logging.Int64("existing-created-at", existingOrder.CreatedAt),
  2263  				logging.Int64("amended-expires-at", amendedOrder.ExpiresAt),
  2264  				logging.Order(*existingOrder))
  2265  		}
  2266  		return nil, nil, types.ErrInvalidExpirationDatetime
  2267  	}
  2268  
  2269  	// if expiration has changed and is not 0, and is before currentTime
  2270  	// then we expire the order
  2271  	if amendedOrder.ExpiresAt != 0 && amendedOrder.ExpiresAt < amendedOrder.UpdatedAt {
  2272  		needToAddExpiry = false
  2273  		// remove the order from the expiring
  2274  		// at this point the order is still referenced at the time of expiry of the existingOrder
  2275  		if existingOrder.IsExpireable() {
  2276  			m.expiringOrders.RemoveOrder(existingOrder.ExpiresAt, amendedOrder.ID)
  2277  		}
  2278  
  2279  		// Update the existing message in place before we cancel it
  2280  		if foundOnBook {
  2281  			// Do not amend in place, the amend could be something
  2282  			// not supported for an amend in place, and not pass
  2283  			// the validation of the order book
  2284  			cancellation, err := m.matching.CancelOrder(existingOrder)
  2285  			if cancellation == nil || err != nil {
  2286  				m.log.Panic("Failure to cancel order from matching engine",
  2287  					logging.String("party-id", amendedOrder.Party),
  2288  					logging.String("order-id", amendedOrder.ID),
  2289  					logging.String("market", m.mkt.ID),
  2290  					logging.Error(err))
  2291  			}
  2292  			m.releaseOrderFromHoldingAccount(ctx, existingOrder.ID, existingOrder.Party, existingOrder.Side)
  2293  		}
  2294  
  2295  		// Update the order in our stores (will be marked as cancelled)
  2296  		// set the proper status
  2297  		amendedOrder.Status = types.OrderStatusExpired
  2298  		m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  2299  		m.removePeggedOrder(amendedOrder)
  2300  
  2301  		return &types.OrderConfirmation{
  2302  			Order: amendedOrder,
  2303  		}, nil, nil
  2304  	}
  2305  
  2306  	if existingOrder.PeggedOrder != nil {
  2307  		// Amend in place during an auction
  2308  		if m.as.InAuction() {
  2309  			ret := m.orderAmendWhenParked(amendedOrder)
  2310  			m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  2311  			return ret, nil, nil
  2312  		}
  2313  		err := m.repricePeggedOrder(amendedOrder)
  2314  		if err != nil {
  2315  			// Failed to reprice so we have to park the order
  2316  			if amendedOrder.Status != types.OrderStatusParked {
  2317  				// If we are live then park
  2318  				m.parkOrder(ctx, existingOrder.ID)
  2319  			}
  2320  			ret := m.orderAmendWhenParked(amendedOrder)
  2321  			m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  2322  			return ret, nil, nil
  2323  		}
  2324  		// We got a new valid price, if we are parked we need to unpark
  2325  		if amendedOrder.Status == types.OrderStatusParked {
  2326  			// we were parked, need to unpark
  2327  			m.peggedOrders.Unpark(amendedOrder.ID)
  2328  			return m.submitValidatedOrder(ctx, amendedOrder)
  2329  		}
  2330  	}
  2331  
  2332  	priceShift := amendedOrder.Price.NEQ(existingOrder.Price)
  2333  	sizeIncrease := amendedOrder.Size > existingOrder.Size
  2334  	sizeDecrease := amendedOrder.Size < existingOrder.Size
  2335  	expiryChange := amendedOrder.ExpiresAt != existingOrder.ExpiresAt
  2336  	timeInForceChange := amendedOrder.TimeInForce != existingOrder.TimeInForce
  2337  
  2338  	// If nothing changed, amend in place to update updatedAt and version number
  2339  	if !priceShift && !sizeIncrease && !sizeDecrease && !expiryChange && !timeInForceChange {
  2340  		ret := m.orderAmendInPlace(existingOrder, amendedOrder)
  2341  		m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  2342  		return ret, nil, nil
  2343  	}
  2344  
  2345  	icebergSizeIncrease := false
  2346  	if amendedOrder.IcebergOrder != nil && sizeIncrease {
  2347  		// iceberg orders size changes can always be done in-place because they either:
  2348  		// 1) decrease the size, which is already done in-place for all orders
  2349  		// 2) increase the size, which only increases the reserved remaining and not the "active" remaining of the iceberg
  2350  		// so we set an icebergSizeIncrease to skip the cancel-replace flow.
  2351  		sizeIncrease = false
  2352  		icebergSizeIncrease = true
  2353  	}
  2354  
  2355  	// if increase in size or change in price
  2356  	// ---> DO atomic cancel and submit
  2357  	if priceShift || sizeIncrease {
  2358  		return m.orderCancelReplace(ctx, existingOrder, amendedOrder)
  2359  	}
  2360  
  2361  	// if decrease in size or change in expiration date
  2362  	// ---> DO amend in place in matching engine
  2363  	if expiryChange || sizeDecrease || timeInForceChange || icebergSizeIncrease {
  2364  		m.releaseOrderFromHoldingAccount(ctx, amendedOrder.ID, amendedOrder.Party, amendedOrder.Side)
  2365  		ret := m.orderAmendInPlace(existingOrder, amendedOrder)
  2366  		m.broker.Send(events.NewOrderEvent(ctx, amendedOrder))
  2367  		amt := m.calculateAmountBySide(ret.Order.Side, ret.Order.Price, ret.Order.TrueRemaining())
  2368  		fees := num.UintZero()
  2369  		var err error
  2370  		if m.as.InAuction() {
  2371  			fees, err = m.calculateFees(ret.Order.Party, ret.Order.TrueRemaining(), ret.Order.Price, ret.Order.Side)
  2372  			if err != nil {
  2373  				return nil, nil, m.unregisterAndReject(ctx, ret.Order, err)
  2374  			}
  2375  		}
  2376  		asset := m.quoteAsset
  2377  		if ret.Order.Side == types.SideSell {
  2378  			asset = m.baseAsset
  2379  		}
  2380  
  2381  		// verify that the party has sufficient funds in their general account to cover for this amount
  2382  		if err := m.collateral.PartyHasSufficientBalance(asset, ret.Order.Party, num.Sum(amt, fees), types.AccountTypeGeneral); err != nil {
  2383  			return nil, nil, err
  2384  		}
  2385  
  2386  		transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, ret.Order.ID, ret.Order.Party, asset, amt, fees, types.AccountTypeGeneral)
  2387  		if err != nil {
  2388  			m.log.Panic("failed to transfer funds to holding account for order", logging.Order(ret.Order), logging.Error(err))
  2389  		}
  2390  		m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer}))
  2391  		return ret, nil, nil
  2392  	}
  2393  
  2394  	// we should never reach this point as amendment was validated before
  2395  	// and every kind should have been handled down here.
  2396  	m.log.Panic(
  2397  		"invalid amend did not match any amendment combination",
  2398  		logging.String("amended-order", amendedOrder.String()),
  2399  		logging.String("existing-order", amendedOrder.String()),
  2400  	)
  2401  
  2402  	return nil, nil, types.ErrEditNotAllowed
  2403  }
  2404  
  2405  func (m *Market) validateOrderAmendment(order *types.Order, amendment *types.OrderAmendment) error {
  2406  	if err := amendment.Validate(); err != nil {
  2407  		return err
  2408  	}
  2409  	// check TIME_IN_FORCE and expiry
  2410  	if amendment.TimeInForce == types.OrderTimeInForceGTT {
  2411  		// if expiresAt is before or equal to created at
  2412  		// we return an error, we know ExpiresAt is set because of amendment.Validate
  2413  		if *amendment.ExpiresAt <= order.CreatedAt {
  2414  			return types.OrderErrorExpiryAtBeforeCreatedAt
  2415  		}
  2416  	}
  2417  
  2418  	if (amendment.TimeInForce == types.OrderTimeInForceGFN ||
  2419  		amendment.TimeInForce == types.OrderTimeInForceGFA) &&
  2420  		amendment.TimeInForce != order.TimeInForce {
  2421  		// We cannot amend to a GFA/GFN orders
  2422  		return types.OrderErrorCannotAmendToGFAOrGFN
  2423  	}
  2424  
  2425  	if (order.TimeInForce == types.OrderTimeInForceGFN ||
  2426  		order.TimeInForce == types.OrderTimeInForceGFA) &&
  2427  		(amendment.TimeInForce != order.TimeInForce &&
  2428  			amendment.TimeInForce != types.OrderTimeInForceUnspecified) {
  2429  		// We cannot amend from a GFA/GFN orders
  2430  		return types.OrderErrorCannotAmendFromGFAOrGFN
  2431  	}
  2432  
  2433  	if order.PeggedOrder == nil {
  2434  		// We cannot change a pegged orders details on a non pegged order
  2435  		if amendment.PeggedOffset != nil ||
  2436  			amendment.PeggedReference != types.PeggedReferenceUnspecified {
  2437  			return types.OrderErrorCannotAmendPeggedOrderDetailsOnNonPeggedOrder
  2438  		}
  2439  	} else if amendment.Price != nil {
  2440  		// We cannot change the price on a pegged order
  2441  		return types.OrderErrorUnableToAmendPriceOnPeggedOrder
  2442  	}
  2443  
  2444  	// if side is buy we need to check that the party has sufficient funds in their general account to cover for the change in quote asset required
  2445  	if order.Side == types.SideBuy && (amendment.Price != nil || amendment.SizeDelta != 0) {
  2446  		remaining := order.Remaining
  2447  		// calculate the effective remaining after the change
  2448  		if amendment.SizeDelta < 0 {
  2449  			if remaining > uint64(-amendment.SizeDelta) {
  2450  				remaining -= uint64(-amendment.SizeDelta)
  2451  			} else {
  2452  				remaining = 0
  2453  			}
  2454  		} else {
  2455  			remaining += uint64(amendment.SizeDelta)
  2456  		}
  2457  
  2458  		// if nothing remains then no need to check anything
  2459  		if remaining == 0 {
  2460  			return nil
  2461  		}
  2462  
  2463  		// if the order is pegged and we're in an auction, we're done here
  2464  		if order.PeggedOrder != nil && m.as.InAuction() {
  2465  			return nil
  2466  		}
  2467  
  2468  		existingHoldingQty, existingHoldingFee := m.orderHoldingTracker.GetCurrentHolding(order.ID)
  2469  		oldHoldingRequirement := num.Sum(existingHoldingQty, existingHoldingFee)
  2470  		newFeesRequirement := num.UintZero()
  2471  		price := order.Price
  2472  		if amendment.Price != nil {
  2473  			price, _ = num.UintFromDecimal(amendment.Price.ToDecimal().Mul(m.priceFactor))
  2474  		}
  2475  		if m.as.InAuction() {
  2476  			newFeesRequirement, _ = m.calculateFees(order.Party, remaining, price, order.Side)
  2477  		}
  2478  		if order.PeggedOrder != nil {
  2479  			p, err := m.getNewPeggedPrice(order)
  2480  			if err != nil {
  2481  				return err
  2482  			}
  2483  			price = p
  2484  		}
  2485  		newHoldingRequirement := num.Sum(m.calculateAmountBySide(order.Side, price, remaining), newFeesRequirement)
  2486  		if newHoldingRequirement.GT(oldHoldingRequirement) {
  2487  			if m.collateral.PartyHasSufficientBalance(m.quoteAsset, order.Party, num.UintZero().Sub(newHoldingRequirement, oldHoldingRequirement), types.AccountTypeGeneral) != nil {
  2488  				return fmt.Errorf("party does not have sufficient balance to cover the trade and fees")
  2489  			}
  2490  		}
  2491  	}
  2492  
  2493  	// if the side is sell and we want to sell more, need to check we're good for it
  2494  	if order.Side == types.SideSell && amendment.SizeDelta > 0 {
  2495  		if m.collateral.PartyHasSufficientBalance(m.baseAsset, order.Party, scaleBaseQuantityToAssetDP(uint64(amendment.SizeDelta), m.baseFactor), types.AccountTypeGeneral) != nil {
  2496  			return fmt.Errorf("party does not have sufficient balance to cover the new size")
  2497  		}
  2498  	}
  2499  
  2500  	return nil
  2501  }
  2502  
  2503  func (m *Market) GetQuoteAsset() string {
  2504  	return m.quoteAsset
  2505  }
  2506  
  2507  func (m *Market) Mkt() *types.Market {
  2508  	return m.mkt
  2509  }
  2510  
  2511  func (m *Market) StopSnapshots() {
  2512  	m.matching.StopSnapshots()
  2513  	m.tsCalc.StopSnapshots()
  2514  	m.liquidityEngine.StopSnapshots()
  2515  	m.orderHoldingTracker.StopSnapshots()
  2516  }
  2517  
  2518  func (m *Market) orderCancelReplace(ctx context.Context, existingOrder, newOrder *types.Order) (conf *types.OrderConfirmation, orders []*types.Order, err error) {
  2519  	wasInAuction := m.as.InAuction()
  2520  	defer func() {
  2521  		if err != nil {
  2522  			return
  2523  		}
  2524  
  2525  		orders = m.handleConfirmation(ctx, conf)
  2526  		m.handleConfirmationPassiveOrders(ctx, conf)
  2527  		// if we were in auction before we handled the transfer already, if we're finished, there's nothing to do here.
  2528  		if !conf.Order.IsFinished() && !wasInAuction {
  2529  			amt := m.calculateAmountBySide(newOrder.Side, newOrder.Price, newOrder.TrueRemaining())
  2530  			asset := m.quoteAsset
  2531  			if newOrder.Side == types.SideSell {
  2532  				asset = m.baseAsset
  2533  			}
  2534  			transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, newOrder.ID, newOrder.Party, asset, amt, num.UintZero(), types.AccountTypeGeneral)
  2535  			if err != nil {
  2536  				m.log.Panic("failed to transfer funds to holding account for order", logging.Order(newOrder), logging.Error(err))
  2537  			}
  2538  			m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer}))
  2539  		}
  2540  		m.broker.Send(events.NewOrderEvent(ctx, conf.Order))
  2541  	}()
  2542  
  2543  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderCancelReplace")
  2544  	defer timer.EngineTimeCounterAdd()
  2545  	// first at this point release the funds of the previous order from holding account
  2546  	// because we may be the aggressor
  2547  	m.releaseOrderFromHoldingAccount(ctx, newOrder.ID, newOrder.Party, newOrder.Side)
  2548  
  2549  	// make sure the order is on the book, this was done by canceling the order initially, but that could
  2550  	// trigger an auction in some cases.
  2551  	if o, err := m.matching.GetOrderByID(existingOrder.ID); err != nil || o == nil {
  2552  		m.log.Panic("Can't CancelReplace, the original order was not found",
  2553  			logging.OrderWithTag(*existingOrder, "existing-order"),
  2554  			logging.Error(err))
  2555  	}
  2556  	// cancel-replace amend during auction is quite simple at this point
  2557  	if m.as.InAuction() {
  2558  		conf, err := m.matching.ReplaceOrder(existingOrder, newOrder)
  2559  		if err != nil {
  2560  			m.log.Panic("unable to submit order", logging.Error(err))
  2561  		}
  2562  		if newOrder.PeggedOrder != nil {
  2563  			m.log.Panic("should never reach this point")
  2564  		}
  2565  
  2566  		amt := m.calculateAmountBySide(newOrder.Side, newOrder.Price, newOrder.TrueRemaining())
  2567  		fees, err := m.calculateFees(newOrder.Party, newOrder.TrueRemaining(), newOrder.Price, newOrder.Side)
  2568  		if err != nil {
  2569  			return nil, nil, m.unregisterAndReject(ctx, newOrder, err)
  2570  		}
  2571  		asset := m.quoteAsset
  2572  		if newOrder.Side == types.SideSell {
  2573  			asset = m.baseAsset
  2574  		}
  2575  		transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, newOrder.ID, newOrder.Party, asset, amt, fees, types.AccountTypeGeneral)
  2576  		if err != nil {
  2577  			m.log.Panic("failed to transfer funds to holding account for order", logging.Order(newOrder), logging.Error(err))
  2578  		}
  2579  		m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer}))
  2580  
  2581  		return conf, nil, nil
  2582  	}
  2583  	// if its an iceberg order with a price change and it is being submitted aggressively
  2584  	// set the visible remaining to the full size
  2585  	if newOrder.IcebergOrder != nil {
  2586  		newOrder.Remaining += newOrder.IcebergOrder.ReservedRemaining
  2587  		newOrder.IcebergOrder.ReservedRemaining = 0
  2588  	}
  2589  
  2590  	trades, err := m.checkPriceAndGetTrades(ctx, newOrder)
  2591  	if err != nil {
  2592  		return nil, nil, errors.New("couldn't insert order in book")
  2593  	}
  2594  
  2595  	// check that the party can afford the trade - if not return error and ignore the update
  2596  	if trades != nil {
  2597  		if err := m.canCoverTradesAndFees(newOrder.Party, newOrder.Side, trades); err != nil {
  2598  			return nil, nil, err
  2599  		}
  2600  	}
  2601  
  2602  	// "hot-swap" of the orders
  2603  	conf, err = m.matching.ReplaceOrder(existingOrder, newOrder)
  2604  	if err != nil {
  2605  		m.log.Panic("unable to submit order", logging.Error(err))
  2606  	}
  2607  
  2608  	// replace the trades in the confirmation to have
  2609  	// the ones with the fees embedded
  2610  	conf.Trades = trades
  2611  	return conf, orders, nil
  2612  }
  2613  
  2614  // orderAmendInPlace amends the order in the order book.
  2615  func (m *Market) orderAmendInPlace(originalOrder, amendOrder *types.Order) *types.OrderConfirmation {
  2616  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "orderAmendInPlace")
  2617  	defer timer.EngineTimeCounterAdd()
  2618  
  2619  	err := m.matching.AmendOrder(originalOrder, amendOrder)
  2620  	if err != nil {
  2621  		// panic here, no good reason for a failure at this point
  2622  		m.log.Panic("Failure after amend order from matching engine (amend-in-place)",
  2623  			logging.OrderWithTag(*amendOrder, "new-order"),
  2624  			logging.OrderWithTag(*originalOrder, "old-order"),
  2625  			logging.Error(err))
  2626  	}
  2627  
  2628  	return &types.OrderConfirmation{
  2629  		Order: amendOrder,
  2630  	}
  2631  }
  2632  
  2633  // orderAmendWhenParked amends a parked pegged order.
  2634  func (m *Market) orderAmendWhenParked(amendOrder *types.Order) *types.OrderConfirmation {
  2635  	amendOrder.Status = types.OrderStatusParked
  2636  	amendOrder.Price = num.UintZero()
  2637  	amendOrder.OriginalPrice = num.UintZero()
  2638  	m.peggedOrders.AmendParked(amendOrder)
  2639  
  2640  	return &types.OrderConfirmation{
  2641  		Order: amendOrder,
  2642  	}
  2643  }
  2644  
  2645  // submitStopOrders gets a status as parameter.
  2646  // this function is used on trigger but also on submission
  2647  // at expiry, so just filters out with a parameter.
  2648  func (m *Market) submitStopOrders(ctx context.Context, stopOrders []*types.StopOrder, status types.StopOrderStatus, idgen common.IDGenerator) []*types.OrderConfirmation {
  2649  	confirmations := []*types.OrderConfirmation{}
  2650  	evts := make([]events.Event, 0, len(stopOrders))
  2651  
  2652  	// might contains both the triggered orders and the expired OCO
  2653  	for _, v := range stopOrders {
  2654  		if v.Status == status {
  2655  			conf, err := m.SubmitOrderWithIDGeneratorAndOrderID(
  2656  				ctx, v.OrderSubmission, v.Party, idgen, idgen.NextID(), false,
  2657  			)
  2658  			if err != nil {
  2659  				// not much we can do at that point, let's log the error and move on?
  2660  				m.log.Error("could not submit stop order",
  2661  					logging.StopOrderSubmission(v),
  2662  					logging.Error(err))
  2663  			}
  2664  			if err == nil && conf != nil {
  2665  				v.OrderID = conf.Order.ID
  2666  				confirmations = append(confirmations, conf)
  2667  			}
  2668  		}
  2669  
  2670  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  2671  	}
  2672  
  2673  	m.broker.SendBatch(evts)
  2674  
  2675  	return confirmations
  2676  }
  2677  
  2678  // removeExpiredOrders remove all expired orders from the order book
  2679  // and also any pegged orders that are parked.
  2680  func (m *Market) removeExpiredStopOrders(ctx context.Context, timestamp int64, idgen common.IDGenerator) []*types.OrderConfirmation {
  2681  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredStopOrders")
  2682  	defer timer.EngineTimeCounterAdd()
  2683  
  2684  	toExpire := m.expiringStopOrders.Expire(timestamp)
  2685  	stopOrders := m.stopOrders.RemoveExpired(toExpire)
  2686  
  2687  	//  ensure any OCO orders are also expire
  2688  	toExpireSet := map[string]struct{}{}
  2689  	for _, v := range toExpire {
  2690  		toExpireSet[v] = struct{}{}
  2691  	}
  2692  
  2693  	for _, so := range stopOrders {
  2694  		if _, ok := toExpireSet[so.ID]; !ok {
  2695  			if so.Expiry.Expires() {
  2696  				m.expiringStopOrders.RemoveOrder(so.Expiry.ExpiresAt.UnixNano(), so.ID)
  2697  			}
  2698  		}
  2699  	}
  2700  
  2701  	updatedAt := m.timeService.GetTimeNow()
  2702  
  2703  	if m.as.InAuction() {
  2704  		m.removeExpiredStopOrdersInAuction(ctx, updatedAt, stopOrders)
  2705  		return nil
  2706  	}
  2707  
  2708  	return m.removeExpiredStopOrdersInContinuous(ctx, updatedAt, stopOrders, idgen)
  2709  }
  2710  
  2711  func (m *Market) removeExpiredStopOrdersInAuction(
  2712  	ctx context.Context,
  2713  	updatedAt time.Time,
  2714  	stopOrders []*types.StopOrder,
  2715  ) {
  2716  	evts := []events.Event{}
  2717  	for _, v := range stopOrders {
  2718  		v.UpdatedAt = updatedAt
  2719  		v.Status = types.StopOrderStatusExpired
  2720  		// nothing to do, can send the event now
  2721  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  2722  	}
  2723  
  2724  	m.broker.SendBatch(evts)
  2725  }
  2726  
  2727  func (m *Market) removeExpiredStopOrdersInContinuous(
  2728  	ctx context.Context,
  2729  	updatedAt time.Time,
  2730  	stopOrders []*types.StopOrder,
  2731  	idgen common.IDGenerator,
  2732  ) []*types.OrderConfirmation {
  2733  	evts := []events.Event{}
  2734  	filteredOCO := []*types.StopOrder{}
  2735  	for _, v := range stopOrders {
  2736  		v.UpdatedAt = updatedAt
  2737  		if v.Status == types.StopOrderStatusExpired && v.Expiry.Expires() && *v.Expiry.ExpiryStrategy == types.StopOrderExpiryStrategySubmit {
  2738  			filteredOCO = append(filteredOCO, v)
  2739  			continue
  2740  		}
  2741  		// nothing to do, can send the event now
  2742  		evts = append(evts, events.NewStopOrderEvent(ctx, v))
  2743  	}
  2744  
  2745  	m.broker.SendBatch(evts)
  2746  
  2747  	return m.submitStopOrders(ctx, filteredOCO, types.StopOrderStatusExpired, idgen)
  2748  }
  2749  
  2750  // RemoveExpiredOrders remove all expired orders from the order book
  2751  // and also any pegged orders that are parked.
  2752  func (m *Market) removeExpiredOrders(ctx context.Context, timestamp int64) []*types.Order {
  2753  	timer := metrics.NewTimeCounter(m.mkt.ID, "market", "RemoveExpiredOrders")
  2754  	defer timer.EngineTimeCounterAdd()
  2755  
  2756  	expired := []*types.Order{}
  2757  	toExp := m.expiringOrders.Expire(timestamp)
  2758  	if len(toExp) == 0 {
  2759  		return expired
  2760  	}
  2761  	ids := make([]string, 0, len(toExp))
  2762  	for _, orderID := range toExp {
  2763  		var order *types.Order
  2764  		// The pegged expiry orders are copies and do not reflect the
  2765  		// current state of the order, therefore we look it up
  2766  		originalOrder, foundOnBook, err := m.getOrderByID(orderID)
  2767  		if err != nil {
  2768  			// nothing to do there.
  2769  			continue
  2770  		}
  2771  		// assign to the order the order from the book
  2772  		// so we get the most recent version from the book
  2773  		// to continue with
  2774  		order = originalOrder
  2775  
  2776  		// if the order was on the book basically
  2777  		// either a pegged + non parked
  2778  		// or a non-pegged order
  2779  		if foundOnBook {
  2780  			m.matching.DeleteOrder(order)
  2781  			// release any outstanding funds from the holding account to the general account
  2782  			m.releaseOrderFromHoldingAccount(ctx, order.ID, order.Party, order.Side)
  2783  		}
  2784  
  2785  		// if this was a pegged order
  2786  		// remove from the pegged / parked list
  2787  		if order.PeggedOrder != nil {
  2788  			m.removePeggedOrder(order)
  2789  		}
  2790  
  2791  		// now we add to the list of expired orders
  2792  		// and assign the appropriate status
  2793  		order.UpdatedAt = m.timeService.GetTimeNow().UnixNano()
  2794  		order.Status = types.OrderStatusExpired
  2795  		expired = append(expired, order)
  2796  		ids = append(ids, orderID)
  2797  	}
  2798  	if len(ids) > 0 {
  2799  		m.broker.Send(events.NewExpiredOrdersEvent(ctx, m.mkt.ID, ids))
  2800  	}
  2801  
  2802  	// If we have removed an expired order, do we need to reprice any
  2803  	// or maybe notify the liquidity engine
  2804  	if len(expired) > 0 && !m.as.InAuction() {
  2805  		m.checkForReferenceMoves(ctx, false)
  2806  	}
  2807  
  2808  	return expired
  2809  }
  2810  
  2811  func (m *Market) getBestStaticAskPrice() (*num.Uint, error) {
  2812  	return m.matching.GetBestStaticAskPrice()
  2813  }
  2814  
  2815  func (m *Market) getBestStaticAskPriceAndVolume() (*num.Uint, uint64, error) {
  2816  	return m.matching.GetBestStaticAskPriceAndVolume()
  2817  }
  2818  
  2819  func (m *Market) getBestStaticBidPrice() (*num.Uint, error) {
  2820  	return m.matching.GetBestStaticBidPrice()
  2821  }
  2822  
  2823  func (m *Market) getBestStaticBidPriceAndVolume() (*num.Uint, uint64, error) {
  2824  	return m.matching.GetBestStaticBidPriceAndVolume()
  2825  }
  2826  
  2827  func (m *Market) getBestStaticPricesDecimal() (bid, ask num.Decimal, err error) {
  2828  	ask = num.DecimalZero()
  2829  	ubid, err := m.getBestStaticBidPrice()
  2830  	if err != nil {
  2831  		bid = num.DecimalZero()
  2832  		return
  2833  	}
  2834  	bid = ubid.ToDecimal()
  2835  	uask, err := m.getBestStaticAskPrice()
  2836  	if err != nil {
  2837  		ask = num.DecimalZero()
  2838  		return
  2839  	}
  2840  	ask = uask.ToDecimal()
  2841  	return
  2842  }
  2843  
  2844  func (m *Market) getStaticMidPrice(side types.Side) (*num.Uint, error) {
  2845  	bid, err := m.matching.GetBestStaticBidPrice()
  2846  	if err != nil {
  2847  		return num.UintZero(), err
  2848  	}
  2849  	ask, err := m.matching.GetBestStaticAskPrice()
  2850  	if err != nil {
  2851  		return num.UintZero(), err
  2852  	}
  2853  	mid := num.UintZero()
  2854  	one := num.NewUint(1)
  2855  	two := num.Sum(one, one)
  2856  	one, _ = num.UintFromDecimal(one.ToDecimal().Mul(m.priceFactor))
  2857  	if side == types.SideBuy {
  2858  		mid = mid.Div(num.Sum(bid, ask, one), two)
  2859  	} else {
  2860  		mid = mid.Div(num.Sum(bid, ask), two)
  2861  	}
  2862  
  2863  	return mid, nil
  2864  }
  2865  
  2866  // removePeggedOrder looks through the pegged and parked list and removes the matching order if found.
  2867  func (m *Market) removePeggedOrder(order *types.Order) {
  2868  	// remove if order was expiring
  2869  	m.expiringOrders.RemoveOrder(order.ExpiresAt, order.ID)
  2870  	// unpark will remove the order from the pegged orders data structure
  2871  	m.peggedOrders.Unpark(order.ID)
  2872  }
  2873  
  2874  // getOrderBy looks for the order in the order book and in the list
  2875  // of pegged orders in the market. Returns the order if found, a bool
  2876  // representing if the order was found on the order book and any error code.
  2877  func (m *Market) getOrderByID(orderID string) (*types.Order, bool, error) {
  2878  	order, err := m.matching.GetOrderByID(orderID)
  2879  	if err == nil {
  2880  		return order, true, nil
  2881  	}
  2882  
  2883  	// The pegged order list contains all the pegged orders in the system
  2884  	// whether they are parked or live. Check this list of a matching order
  2885  	if o := m.peggedOrders.GetParkedByID(orderID); o != nil {
  2886  		return o, false, nil
  2887  	}
  2888  
  2889  	// We couldn't find it
  2890  	return nil, false, common.ErrOrderNotFound
  2891  }
  2892  
  2893  func (m *Market) getTargetStake() *num.Uint {
  2894  	return m.tsCalc.GetTargetStake(m.timeService.GetTimeNow())
  2895  }
  2896  
  2897  func (m *Market) getSuppliedStake() *num.Uint {
  2898  	return m.liquidity.CalculateSuppliedStake()
  2899  }
  2900  
  2901  // canTrade returns true if the market state is active pending or suspended.
  2902  func (m *Market) canTrade() bool {
  2903  	return m.mkt.State == types.MarketStateActive ||
  2904  		m.mkt.State == types.MarketStatePending ||
  2905  		m.mkt.State == types.MarketStateSuspended ||
  2906  		m.mkt.State == types.MarketStateSuspendedViaGovernance
  2907  }
  2908  
  2909  func (m *Market) canSubmitMaybeSell(party string, side types.Side) bool {
  2910  	// buy side
  2911  	// or network party
  2912  	// or no empty allowedSellers list
  2913  	// are always fine
  2914  	if len(m.allowedSellers) <= 0 || side == types.SideBuy || party == types.NetworkParty {
  2915  		return true
  2916  	}
  2917  
  2918  	_, isAllowed := m.allowedSellers[party]
  2919  	return isAllowed
  2920  }
  2921  
  2922  // cleanupOnReject removes all resources created while the market was on PREPARED state.
  2923  // at this point no fees would have been collected or anything like this.
  2924  func (m *Market) cleanupOnReject(ctx context.Context) {
  2925  	m.stopAllLiquidityProvisionOnReject(ctx)
  2926  
  2927  	stopOrders := m.stopOrders.Settled()
  2928  	evts := make([]events.Event, 0, len(stopOrders))
  2929  	for _, o := range stopOrders {
  2930  		evts = append(evts, events.NewStopOrderEvent(ctx, o))
  2931  	}
  2932  	if len(evts) > 0 {
  2933  		m.broker.SendBatch(evts)
  2934  	}
  2935  
  2936  	tresps, err := m.collateral.ClearSpotMarket(ctx, m.GetID(), m.quoteAsset, m.getParties())
  2937  	if err != nil {
  2938  		m.log.Panic("unable to cleanup a rejected market",
  2939  			logging.String("market-id", m.GetID()),
  2940  			logging.Error(err))
  2941  		return
  2942  	}
  2943  
  2944  	m.stateVarEngine.UnregisterStateVariable(m.quoteAsset, m.mkt.ID)
  2945  	if len(tresps) > 0 {
  2946  		m.broker.Send(events.NewLedgerMovements(ctx, tresps))
  2947  	}
  2948  }
  2949  
  2950  func (m *Market) stopAllLiquidityProvisionOnReject(ctx context.Context) {
  2951  	m.liquidity.StopAllLiquidityProvision(ctx)
  2952  }
  2953  
  2954  // GetTotalOrderBookLevelCount returns the total number of levels in the order book.
  2955  func (m *Market) GetTotalOrderBookLevelCount() uint64 {
  2956  	return m.matching.GetOrderBookLevelCount()
  2957  }
  2958  
  2959  // GetTotalPeggedOrderCount returns the total number of pegged orders.
  2960  func (m *Market) GetTotalPeggedOrderCount() uint64 {
  2961  	return m.matching.GetPeggedOrdersCount()
  2962  }
  2963  
  2964  // GetTotalStopOrderCount returns the total number of stop orders.
  2965  func (m *Market) GetTotalStopOrderCount() uint64 {
  2966  	return m.stopOrders.GetStopOrderCount()
  2967  }
  2968  
  2969  // GetTotalOpenPositionCount returns the total number of open positions.
  2970  func (m *Market) GetTotalOpenPositionCount() uint64 {
  2971  	return 0
  2972  }
  2973  
  2974  // GetTotalLPShapeCount returns the total number of LP shapes.
  2975  func (m *Market) GetTotalLPShapeCount() uint64 {
  2976  	return 9
  2977  }
  2978  
  2979  // getCurrentMarkPrice returns the current mark price.
  2980  func (m *Market) getCurrentMarkPrice() *num.Uint {
  2981  	m.markPriceLock.RLock()
  2982  	defer m.markPriceLock.RUnlock()
  2983  	if m.markPrice == nil {
  2984  		return num.UintZero()
  2985  	}
  2986  	return m.markPrice.Clone()
  2987  }
  2988  
  2989  // getLastTradedPrice returns the last traded price.
  2990  func (m *Market) getLastTradedPrice() *num.Uint {
  2991  	if m.lastTradedPrice == nil {
  2992  		return num.UintZero()
  2993  	}
  2994  	return m.lastTradedPrice.Clone()
  2995  }
  2996  
  2997  // spot specific stuff
  2998  
  2999  // processFeesTransfersOnEnterAuction handles the transfer from general account to holding account of fees to cover the trades that can take place
  3000  // during auction. This is necessary as during auction the fees are split between the participating parties of a trade rather than paid by the aggressor.
  3001  func (m *Market) processFeesTransfersOnEnterAuction(ctx context.Context) {
  3002  	parties := make([]string, 0, len(m.parties))
  3003  	for v := range m.parties {
  3004  		parties = append(parties, v)
  3005  	}
  3006  	sort.Strings(parties)
  3007  	ordersToCancel := []*types.Order{}
  3008  	transfers := []*types.LedgerMovement{}
  3009  	for _, party := range parties {
  3010  		orders := m.matching.GetOrdersPerParty(party)
  3011  		for _, o := range orders {
  3012  			if o.Side == types.SideSell {
  3013  				continue
  3014  			}
  3015  			// if the side is buy then the fees are paid directly by the buyer which must have an account in quote asset
  3016  			// with sufficient funds
  3017  			fees, err := m.calculateFees(party, o.TrueRemaining(), o.Price, o.Side)
  3018  			if err != nil {
  3019  				m.log.Error("error calculating fees for order", logging.Order(o), logging.Error(err))
  3020  				ordersToCancel = append(ordersToCancel, o)
  3021  				continue
  3022  			}
  3023  			if fees.IsZero() {
  3024  				continue
  3025  			}
  3026  			if err := m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, fees, types.AccountTypeGeneral); err != nil {
  3027  				m.log.Error("party has insufficient funds to cover for fees for order", logging.Order(o), logging.Error(err))
  3028  				ordersToCancel = append(ordersToCancel, o)
  3029  				continue
  3030  			}
  3031  			// party has sufficient funds to cover for fees - transfer fees from the party general account to the party holding account
  3032  			transfer, err := m.orderHoldingTracker.TransferFeeToHoldingAccount(ctx, o.ID, party, m.quoteAsset, fees, types.AccountTypeGeneral)
  3033  			if err != nil {
  3034  				m.log.Error("failed to transfer from general account to holding account", logging.Order(o), logging.Error(err))
  3035  				ordersToCancel = append(ordersToCancel, o)
  3036  				continue
  3037  			}
  3038  			transfers = append(transfers, transfer)
  3039  		}
  3040  	}
  3041  	if len(transfers) > 0 {
  3042  		m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  3043  	}
  3044  	// cancel all orders with insufficient funds
  3045  	for _, o := range ordersToCancel {
  3046  		m.cancelOrder(ctx, o.Party, o.ID)
  3047  	}
  3048  }
  3049  
  3050  func (m *Market) checkFeeTransfersWhileInAuction(ctx context.Context) {
  3051  	parties := make([]string, 0, len(m.parties))
  3052  	for v := range m.parties {
  3053  		parties = append(parties, v)
  3054  	}
  3055  	sort.Strings(parties)
  3056  	ordersToCancel := []*types.Order{}
  3057  	transfers := []*types.LedgerMovement{}
  3058  	for _, party := range parties {
  3059  		orders := m.matching.GetOrdersPerParty(party)
  3060  		for _, o := range orders {
  3061  			if o.Side == types.SideSell {
  3062  				continue
  3063  			}
  3064  			// if the side is buy then the fees are paid directly by the buyer which must have an account in quote asset
  3065  			// with sufficient funds
  3066  			fees, err := m.calculateFees(party, o.TrueRemaining(), o.Price, o.Side)
  3067  			if err != nil {
  3068  				m.log.Error("error calculating fees for order", logging.Order(o), logging.Error(err))
  3069  				ordersToCancel = append(ordersToCancel, o)
  3070  				continue
  3071  			}
  3072  			if fees.IsZero() {
  3073  				continue
  3074  			}
  3075  			// check if we have already handled this fee and if so that it matches
  3076  			_, paidFees := m.orderHoldingTracker.GetCurrentHolding(o.ID)
  3077  
  3078  			if fees.GT(paidFees) {
  3079  				// We need to recalculate the fees amount
  3080  				var newFees num.Uint
  3081  				newFees.Sub(fees, paidFees)
  3082  				accType := types.AccountTypeGeneral
  3083  				if party == types.NetworkParty {
  3084  					var err error
  3085  					accType, _, err = m.getACcountTypesForPAP()
  3086  					if err != nil {
  3087  						m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", o.ID))
  3088  					}
  3089  				}
  3090  				if err := m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, &newFees, accType); err != nil {
  3091  					m.log.Error("party has insufficient funds to cover for fees for order", logging.Order(o), logging.Error(err))
  3092  					ordersToCancel = append(ordersToCancel, o)
  3093  					continue
  3094  				}
  3095  				// party has sufficient funds to cover for fees - transfer fees from the party general account to the party holding account
  3096  				transfer, err := m.orderHoldingTracker.TransferFeeToHoldingAccount(ctx, o.ID, party, m.quoteAsset, &newFees, accType)
  3097  				if err != nil {
  3098  					m.log.Error("failed to transfer from general account to holding account", logging.Order(o), logging.Error(err))
  3099  					ordersToCancel = append(ordersToCancel, o)
  3100  					continue
  3101  				}
  3102  				transfers = append(transfers, transfer)
  3103  			}
  3104  		}
  3105  	}
  3106  	if len(transfers) > 0 {
  3107  		m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  3108  	}
  3109  	// cancel all orders with insufficient funds
  3110  	for _, o := range ordersToCancel {
  3111  		m.cancelOrder(ctx, o.Party, o.ID)
  3112  	}
  3113  }
  3114  
  3115  // processFeesReleaseOnLeaveAuction releases any fees locked for the duration of an auction.
  3116  func (m *Market) processFeesReleaseOnLeaveAuction(ctx context.Context) {
  3117  	parties := make([]string, 0, len(m.parties))
  3118  	for v := range m.parties {
  3119  		parties = append(parties, v)
  3120  	}
  3121  	sort.Strings(parties)
  3122  	transfers := []*types.LedgerMovement{}
  3123  	for _, party := range parties {
  3124  		orders := m.matching.GetOrdersPerParty(party)
  3125  		for _, o := range orders {
  3126  			if o.Side == types.SideBuy && o.PeggedOrder == nil {
  3127  				accType := types.AccountTypeGeneral
  3128  				if party == types.NetworkParty {
  3129  					var err error
  3130  					accType, _, err = m.getACcountTypesForPAP()
  3131  					if err != nil {
  3132  						m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", o.ID))
  3133  					}
  3134  				}
  3135  				transfer, err := m.orderHoldingTracker.ReleaseFeeFromHoldingAccount(ctx, o.ID, party, m.quoteAsset, accType)
  3136  				if err != nil {
  3137  					// it's valid, if fees for the order were zero, we don't update the holding account
  3138  					// cache so it can be legitimately not there.
  3139  					continue
  3140  				}
  3141  				transfers = append(transfers, transfer)
  3142  			}
  3143  		}
  3144  	}
  3145  	if len(transfers) > 0 {
  3146  		m.broker.Send(events.NewLedgerMovements(ctx, transfers))
  3147  	}
  3148  }
  3149  
  3150  func (m *Market) handleTrade(ctx context.Context, trade *types.Trade) []*types.LedgerMovement {
  3151  	transfers := []*types.LedgerMovement{}
  3152  	// we need to transfer base from the seller to the buyer,
  3153  	// quote from buyer to the seller.
  3154  	// if we're in auction we first need to release the fee funds for the buyer
  3155  	// and release the funds for both sides from the holding accounts.
  3156  	fees, err := m.calculateFeesForTrades([]*types.Trade{trade})
  3157  	if err != nil {
  3158  		m.log.Panic("failed to calculate fees for trade", logging.Trade(trade))
  3159  	}
  3160  	if trade.Aggressor == types.SideUnspecified {
  3161  		fee := num.UintZero()
  3162  		if fees != nil {
  3163  			fee = fees.TotalFeesAmountPerParty()[trade.Buyer]
  3164  		}
  3165  
  3166  		// release buyer's trade + fees quote quantity from the holding account
  3167  		var transfer *types.LedgerMovement
  3168  		var err error
  3169  		// in auction the buyer and seller split the fees, but that just means that total they need to have in the holding account
  3170  		// is still quantity + fees/2 because the other half of the fees (the seller half) is paid out of what is supposed to go to the seller
  3171  		accType := types.AccountTypeGeneral
  3172  		if trade.Buyer == types.NetworkParty {
  3173  			accType, _, err = m.getACcountTypesForPAP()
  3174  			if err != nil {
  3175  				m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.SellOrder))
  3176  			}
  3177  		}
  3178  		transfer, err = m.orderHoldingTracker.ReleaseQuantityHoldingAccountAuctionEnd(ctx, trade.BuyOrder, trade.Buyer, m.quoteAsset, scaleQuoteQuantityToAssetDP(trade.Size, trade.Price, m.positionFactor), fee, accType)
  3179  
  3180  		if err != nil {
  3181  			m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade), logging.Error(err))
  3182  		}
  3183  		transfers = append(transfers, transfer)
  3184  
  3185  		accType = types.AccountTypeGeneral
  3186  		if trade.Seller == types.NetworkParty {
  3187  			accType, _, err = m.getACcountTypesForPAP()
  3188  			if err != nil {
  3189  				m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.SellOrder))
  3190  			}
  3191  		}
  3192  		// release seller's base quantity from the holding account
  3193  		transfer, err = m.orderHoldingTracker.ReleaseQuantityHoldingAccount(ctx, trade.SellOrder, trade.Seller, m.baseAsset, scaleBaseQuantityToAssetDP(trade.Size, m.baseFactor), num.UintZero(), accType)
  3194  		if err != nil {
  3195  			m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade), logging.Error(err))
  3196  		}
  3197  		transfers = append(transfers, transfer)
  3198  	} else {
  3199  		// if there is an aggressor, then we need to release the passive side from the holding account
  3200  		if trade.Aggressor == types.SideSell { // the aggressor is the seller so we need to release funds for the buyer from holding
  3201  			transfer, err := m.orderHoldingTracker.ReleaseQuantityHoldingAccount(ctx, trade.BuyOrder, trade.Buyer, m.quoteAsset, scaleQuoteQuantityToAssetDP(trade.Size, trade.Price, m.positionFactor), num.UintZero(), types.AccountTypeGeneral)
  3202  			if err != nil {
  3203  				m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade))
  3204  			}
  3205  			transfers = append(transfers, transfer)
  3206  		} else { // the aggressor is the buyer, we release the funds for the seller from holding account
  3207  			transfer, err := m.orderHoldingTracker.ReleaseQuantityHoldingAccount(ctx, trade.SellOrder, trade.Seller, m.baseAsset, scaleBaseQuantityToAssetDP(trade.Size, m.baseFactor), num.UintZero(), types.AccountTypeGeneral)
  3208  			if err != nil {
  3209  				m.log.Panic("failed to release funds from holding account for trade", logging.Trade(trade))
  3210  			}
  3211  			transfers = append(transfers, transfer)
  3212  		}
  3213  	}
  3214  
  3215  	// transfer base to buyer
  3216  	baseFromAccountType := types.AccountTypeGeneral
  3217  	baseToAccountType := types.AccountTypeGeneral
  3218  	quoteFromAccountType := types.AccountTypeGeneral
  3219  	quoteToAccountType := types.AccountTypeGeneral
  3220  
  3221  	if trade.Seller == types.NetworkParty {
  3222  		// if the network is the seller, then we want to transfer the base asset from the fromAccountType and the quote asset to the toAccountType
  3223  		baseFromAccountType, quoteToAccountType, err = m.getACcountTypesForPAP()
  3224  		if err != nil {
  3225  			m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.SellOrder))
  3226  		}
  3227  	} else if trade.Buyer == types.NetworkParty {
  3228  		// if the network is the buyer, then we want to transfer the quote base asset to the toAccountType and the quote asset from the fromAccountType
  3229  		quoteFromAccountType, baseToAccountType, err = m.getACcountTypesForPAP()
  3230  		if err != nil {
  3231  			m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", trade.BuyOrder))
  3232  		}
  3233  	}
  3234  	transfer, err := m.collateral.TransferSpot(ctx, trade.Seller, trade.Buyer, m.baseAsset, scaleBaseQuantityToAssetDP(trade.Size, m.baseFactor), baseFromAccountType, baseToAccountType)
  3235  	if err != nil {
  3236  		m.log.Panic("failed to complete spot transfer", logging.Trade(trade))
  3237  	}
  3238  	transfers = append(transfers, transfer)
  3239  	// transfer quote (potentially minus fees) to the seller
  3240  	transfer, err = m.collateral.TransferSpot(ctx, trade.Buyer, trade.Seller, m.quoteAsset, scaleQuoteQuantityToAssetDP(trade.Size, trade.Price, m.positionFactor), quoteFromAccountType, quoteToAccountType)
  3241  	if err != nil {
  3242  		m.log.Panic("failed to complete spot transfer", logging.Trade(trade))
  3243  	}
  3244  	transfers = append(transfers, transfer)
  3245  	if fees != nil {
  3246  		m.applyFees(ctx, fees, quoteToAccountType)
  3247  	}
  3248  	return transfers
  3249  }
  3250  
  3251  // transferToHoldingAccount transfers the remaining funds + fees if needed from the general account to the holding account.
  3252  func (m *Market) transferToHoldingAccount(ctx context.Context, order *types.Order) error {
  3253  	var err error
  3254  	amt := m.calculateAmountBySide(order.Side, order.Price, order.TrueRemaining())
  3255  	fees := num.UintZero()
  3256  	if m.as.InAuction() && order.Side == types.SideBuy {
  3257  		fees, err = m.calculateFees(order.Party, order.TrueRemaining(), order.Price, order.Side)
  3258  		if err != nil {
  3259  			return err
  3260  		}
  3261  	}
  3262  	asset := m.quoteAsset
  3263  	if order.Side == types.SideSell {
  3264  		asset = m.baseAsset
  3265  	}
  3266  
  3267  	accountType := types.AccountTypeGeneral
  3268  	if order.Party == types.NetworkParty {
  3269  		accountType, _, err = m.getACcountTypesForPAP()
  3270  		if err != nil {
  3271  			m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", order.ID))
  3272  		}
  3273  	}
  3274  
  3275  	// verify that the party has sufficient funds in their general account to cover for this amount
  3276  	if err := m.collateral.PartyHasSufficientBalance(asset, order.Party, num.Sum(amt, fees), accountType); err != nil {
  3277  		return err
  3278  	}
  3279  
  3280  	transfer, err := m.orderHoldingTracker.TransferToHoldingAccount(ctx, order.ID, order.Party, asset, amt, fees, accountType)
  3281  	if err != nil {
  3282  		m.log.Panic("failed to transfer funds to holding account for order", logging.Order(order), logging.Error(err))
  3283  	}
  3284  	m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer}))
  3285  	return nil
  3286  }
  3287  
  3288  // releaseOrderFromHoldingAccount release all funds for a given order from holding account. If there are no funds to release it panics.
  3289  func (m *Market) releaseOrderFromHoldingAccount(ctx context.Context, orderID, party string, side types.Side) {
  3290  	asset := m.quoteAsset
  3291  	if side == types.SideSell {
  3292  		asset = m.baseAsset
  3293  	}
  3294  	accType := types.AccountTypeGeneral
  3295  	if party == types.NetworkParty {
  3296  		var err error
  3297  		accType, _, err = m.getACcountTypesForPAP()
  3298  		if err != nil {
  3299  			m.log.Panic("failed to get account types for automated purchase", logging.String("order-id", orderID))
  3300  		}
  3301  	}
  3302  	transfer, err := m.orderHoldingTracker.ReleaseAllFromHoldingAccount(ctx, orderID, party, asset, accType)
  3303  	if err != nil {
  3304  		m.log.Panic("could not release funds from holding account", logging.String("order-id", orderID), logging.Error(err))
  3305  	}
  3306  	if transfer != nil {
  3307  		m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{transfer}))
  3308  	}
  3309  }
  3310  
  3311  // calculateFees calculate the amount of fees a party is due to pay given a side/price/size.
  3312  // during opening auction there are no fees.
  3313  func (m *Market) calculateFees(party string, size uint64, price *num.Uint, side types.Side) (*num.Uint, error) {
  3314  	if m.as.IsOpeningAuction() {
  3315  		return num.UintZero(), nil
  3316  	}
  3317  
  3318  	fakeTrade := &types.Trade{
  3319  		Size:      size,
  3320  		Price:     price,
  3321  		Aggressor: side,
  3322  	}
  3323  	if side == types.SideBuy {
  3324  		fakeTrade.Buyer = party
  3325  	} else {
  3326  		fakeTrade.Seller = party
  3327  	}
  3328  
  3329  	fees, err := m.calculateFeesForTrades([]*types.Trade{fakeTrade})
  3330  	if err != nil {
  3331  		return num.UintZero(), err
  3332  	}
  3333  
  3334  	// if we're in uncrossing governance auction fees will be nil
  3335  	if fees == nil {
  3336  		return num.UintZero(), nil
  3337  	}
  3338  
  3339  	return fees.TotalFeesAmountPerParty()[party], err
  3340  }
  3341  
  3342  func (m *Market) calculateFeesForTrades(trades []*types.Trade) (events.FeesTransfer, error) {
  3343  	var (
  3344  		fees events.FeesTransfer
  3345  		err  error
  3346  	)
  3347  	if !m.as.InAuction() {
  3348  		fees, err = m.fee.CalculateForContinuousMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService)
  3349  	} else if m.as.IsMonitorAuction() || m.as.IsPAPAuction() {
  3350  		// we are in auction mode
  3351  		fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService)
  3352  	} else if m.as.IsFBA() {
  3353  		fees, err = m.fee.CalculateForFrequentBatchesAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService)
  3354  	} else {
  3355  		if !m.as.IsOpeningAuction() {
  3356  			fees, err = m.fee.CalculateForAuctionMode(trades, m.referralDiscountRewardService, m.volumeDiscountService, m.volumeRebateService)
  3357  		}
  3358  	}
  3359  	return fees, err
  3360  }
  3361  
  3362  // calculateAmountBySide calculates the amount *excluding* fees in the asset decimals.
  3363  func (m *Market) calculateAmountBySide(side types.Side, price *num.Uint, size uint64) *num.Uint {
  3364  	if side == types.SideBuy {
  3365  		return num.Sum(scaleQuoteQuantityToAssetDP(size, price, m.positionFactor))
  3366  	}
  3367  	return scaleBaseQuantityToAssetDP(size, m.baseFactor)
  3368  }
  3369  
  3370  // checkSufficientFunds checks if the aggressor party has in their general account sufficient funds to cover the trade + fees.
  3371  func (m *Market) checkSufficientFunds(party string, side types.Side, price *num.Uint, size uint64, isPegged bool, accountType types.AccountType) error {
  3372  	required := m.calculateAmountBySide(side, price, size)
  3373  	if side == types.SideBuy {
  3374  		fees := num.UintZero()
  3375  		var err error
  3376  		if !isPegged {
  3377  			fees, err = m.calculateFees(party, size, price, side)
  3378  			if err != nil {
  3379  				return err
  3380  			}
  3381  		}
  3382  
  3383  		if m.collateral.PartyHasSufficientBalance(m.quoteAsset, party, num.Sum(required, fees), accountType) != nil {
  3384  			return fmt.Errorf("party does not have sufficient balance to cover the trade and fees")
  3385  		}
  3386  	} else {
  3387  		if m.collateral.PartyHasSufficientBalance(m.baseAsset, party, required, accountType) != nil {
  3388  			return fmt.Errorf("party does not have sufficient balance to cover the trade and fees")
  3389  		}
  3390  	}
  3391  	return nil
  3392  }
  3393  
  3394  // convert the quantity to be transferred to the buyer to the base asset decimals.
  3395  func scaleBaseQuantityToAssetDP(sizeUint uint64, baseFactor num.Decimal) *num.Uint {
  3396  	size := num.NewUint(sizeUint)
  3397  	total := size.ToDecimal().Mul(baseFactor)
  3398  	totalI, _ := num.UintFromDecimal(total)
  3399  	return totalI
  3400  }
  3401  
  3402  // convert the quantity to be transferred to the seller to the quote asset decimals.
  3403  func scaleQuoteQuantityToAssetDP(sizeUint uint64, priceInAssetDP *num.Uint, positionFactor num.Decimal) *num.Uint {
  3404  	size := num.NewUint(sizeUint)
  3405  	total := size.Mul(priceInAssetDP, size).ToDecimal().Div(positionFactor)
  3406  	totalI, _ := num.UintFromDecimal(total)
  3407  	return totalI
  3408  }
  3409  
  3410  // closeSpotMarket terminates a market - this can be triggered only via governance.
  3411  func (m *Market) closeSpotMarket(ctx context.Context) {
  3412  	if m.mkt.State != types.MarketStatePending {
  3413  		m.markPriceLock.Lock()
  3414  		m.markPrice = m.lastTradedPrice
  3415  		m.markPriceLock.Unlock()
  3416  		if m.pap != nil {
  3417  			m.stopPAP(ctx)
  3418  		}
  3419  		if err := m.closeMarket(ctx); err != nil {
  3420  			m.log.Error("could not close market", logging.Error(err))
  3421  		}
  3422  		return
  3423  	}
  3424  	for party := range m.parties {
  3425  		_, err := m.CancelAllOrders(ctx, party)
  3426  		if err != nil {
  3427  			m.log.Debug("could not cancel orders for party", logging.PartyID(party), logging.Error(err))
  3428  		}
  3429  	}
  3430  	err := m.closeCancelledMarket(ctx)
  3431  	if err != nil {
  3432  		m.log.Debug("could not close market", logging.MarketID(m.GetID()))
  3433  		return
  3434  	}
  3435  }
  3436  
  3437  func (m *Market) OnEpochEvent(ctx context.Context, epoch types.Epoch) {
  3438  	if m.closed {
  3439  		return
  3440  	}
  3441  	if epoch.Action == vega.EpochAction_EPOCH_ACTION_START {
  3442  		m.liquidity.UpdateSLAParameters(m.mkt.LiquiditySLAParams)
  3443  		m.liquidity.OnEpochStart(ctx, m.timeService.GetTimeNow(), m.markPrice, m.midPrice(), m.getTargetStake(), m.positionFactor)
  3444  		m.epoch = epoch
  3445  	} else if epoch.Action == vega.EpochAction_EPOCH_ACTION_END {
  3446  		if !m.finalFeesDistributed {
  3447  			m.liquidity.OnEpochEnd(ctx, m.timeService.GetTimeNow(), epoch)
  3448  		}
  3449  		m.updateLiquidityFee(ctx)
  3450  
  3451  		m.banking.RegisterTradingFees(ctx, m.quoteAsset, m.fee.TotalTradingFeesPerParty())
  3452  
  3453  		quoteAssetQuantum, _ := m.collateral.GetAssetQuantum(m.quoteAsset)
  3454  		feesStats := m.fee.GetFeesStatsOnEpochEnd(quoteAssetQuantum)
  3455  		feesStats.EpochSeq = epoch.Seq
  3456  		feesStats.Market = m.GetID()
  3457  
  3458  		m.broker.Send(events.NewFeesStatsEvent(ctx, feesStats))
  3459  	}
  3460  }
  3461  
  3462  func (m *Market) OnEpochRestore(ctx context.Context, epoch types.Epoch) {
  3463  	m.epoch = epoch
  3464  	m.liquidityEngine.OnEpochRestore(epoch)
  3465  }
  3466  
  3467  func (m *Market) GetMarketCounters() *types.MarketCounters {
  3468  	return &types.MarketCounters{
  3469  		StopOrderCounter:    m.GetTotalStopOrderCount(),
  3470  		PeggedOrderCounter:  m.GetTotalPeggedOrderCount(),
  3471  		OrderbookLevelCount: m.GetTotalOrderBookLevelCount(),
  3472  		PositionCount:       0,
  3473  	}
  3474  }
  3475  
  3476  func (m *Market) SubmitAMM(context.Context, *types.SubmitAMM, string) error {
  3477  	return errors.New("unimplemented")
  3478  }
  3479  
  3480  func (m *Market) AmendAMM(context.Context, *types.AmendAMM, string) error {
  3481  	return errors.New("unimplemented")
  3482  }
  3483  
  3484  func (m *Market) CancelAMM(context.Context, *types.CancelAMM, string) error {
  3485  	return errors.New("unimplemented")
  3486  }
  3487  
  3488  func (m *Market) ValidateSettlementData(_ *num.Uint) bool {
  3489  	return true
  3490  }
  3491  
  3492  // IDGen is an id generator for orders.
  3493  type IDGen interface {
  3494  	NextID() string
  3495  }
  3496  
  3497  func (m *Market) checkOrderForSpam(side types.Side, orderPrice *num.Uint, orderSize uint64, peggedOrder *types.PeggedOrder, orderType vega.Order_Type, quantumMultiplier num.Decimal) error {
  3498  	assetQuantum, err := m.collateral.GetAssetQuantum(m.quoteAsset)
  3499  	if err != nil {
  3500  		return err
  3501  	}
  3502  
  3503  	var price *num.Uint
  3504  	if peggedOrder != nil || orderType == vega.Order_TYPE_MARKET {
  3505  		priceInMarket, _ := num.UintFromDecimal(m.getCurrentMarkPrice().ToDecimal().Div(m.priceFactor))
  3506  		offset := num.UintZero()
  3507  		if peggedOrder != nil {
  3508  			offset = peggedOrder.Offset
  3509  		}
  3510  		if side == types.SideBuy {
  3511  			priceInMarket.AddSum(offset)
  3512  		} else {
  3513  			priceInMarket = priceInMarket.Sub(priceInMarket, offset)
  3514  		}
  3515  		price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
  3516  	} else {
  3517  		price, _ = num.UintFromDecimal(orderPrice.ToDecimal().Mul(m.priceFactor))
  3518  	}
  3519  
  3520  	value := num.UintZero().Mul(num.NewUint(orderSize), price).ToDecimal()
  3521  	value = value.Div(m.positionFactor)
  3522  	required := assetQuantum.Mul(quantumMultiplier)
  3523  	if value.LessThan(required) {
  3524  		return fmt.Errorf(fmt.Sprintf("order value (%s) is less than minimum holding requirement for spam (%s)", value.String(), required.String()))
  3525  	}
  3526  	return nil
  3527  }
  3528  
  3529  func (m *Market) checkOrderAmendForSpam(order *types.Order) error {
  3530  	return m.checkOrderForSpam(
  3531  		order.Side,
  3532  		order.Price,
  3533  		order.Size,
  3534  		order.PeggedOrder,
  3535  		order.Type,
  3536  		m.minHoldingQuantumMultiplier)
  3537  }
  3538  
  3539  func (m *Market) CheckOrderSubmissionForSpam(orderSubmission *types.OrderSubmission, party string, quantumMultiplier num.Decimal) error {
  3540  	return m.checkOrderForSpam(
  3541  		orderSubmission.Side,
  3542  		orderSubmission.Price,
  3543  		orderSubmission.Size,
  3544  		orderSubmission.PeggedOrder,
  3545  		orderSubmission.Type,
  3546  		quantumMultiplier)
  3547  }
  3548  
  3549  func (m *Market) enterAutomatedPurchaseAuction(ctx context.Context, orderID string, orderSide types.Side, orderPrice *num.Uint, orderSize uint64, reference string, duration time.Duration) (string, error) {
  3550  	if !m.canTrade() {
  3551  		return "", fmt.Errorf(fmt.Sprintf("cannot trade in market %s", m.mkt.ID))
  3552  	}
  3553  
  3554  	// if we're in governance auction we're not placing automated purchase orders because we don't have
  3555  	// control over when the market will be resumed
  3556  	if m.mkt.TradingMode == types.MarketTradingModeSuspendedViaGovernance {
  3557  		return "", fmt.Errorf(fmt.Sprintf("cannot enter auction in market %s - market is in governance suspension", m.mkt.ID))
  3558  	}
  3559  
  3560  	if m.as.InAuction() {
  3561  		now := m.timeService.GetTimeNow()
  3562  		aRemaining := int64(m.as.ExpiresAt().Sub(now) / time.Second)
  3563  		if aRemaining >= int64(duration.Seconds()) {
  3564  			return "", nil
  3565  		}
  3566  		m.as.ExtendAuctionAutomatedPurchase(types.AuctionDuration{
  3567  			Duration: int64(duration.Seconds()) - aRemaining,
  3568  		})
  3569  		if evt := m.as.AuctionExtended(ctx, now); evt != nil {
  3570  			m.broker.Send(evt)
  3571  		}
  3572  	} else {
  3573  		m.as.StartAutomatedPurchaseAuction(m.timeService.GetTimeNow(), int64(duration.Seconds()))
  3574  		m.mkt.TradingMode = types.MarketTradingModeAutomatedPuchaseAuction
  3575  		m.mkt.State = types.MarketStateSuspended
  3576  		m.enterAuction(ctx)
  3577  		m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt))
  3578  	}
  3579  	// if we were able to enter an auction, now place the order for the network account
  3580  	if m.as.InAuction() {
  3581  		os := &types.OrderSubmission{
  3582  			MarketID:    m.mkt.ID,
  3583  			Price:       orderPrice.Clone(),
  3584  			Size:        orderSize,
  3585  			Side:        orderSide,
  3586  			TimeInForce: types.OrderTimeInForceGFA,
  3587  			Type:        types.OrderTypeLimit,
  3588  			Reference:   reference,
  3589  		}
  3590  		conf, err := m.SubmitOrder(ctx, os, types.NetworkParty, orderID)
  3591  		if err != nil {
  3592  			return "", err
  3593  		}
  3594  		return conf.Order.ID, nil
  3595  	}
  3596  	return "", nil
  3597  }
  3598  
  3599  func (m *Market) getACcountTypesForPAP() (types.AccountType, types.AccountType, error) {
  3600  	if m.pap == nil {
  3601  		return types.AccountTypeUnspecified, types.AccountTypeUnspecified, fmt.Errorf("protocol automated purchase not defined for market")
  3602  	}
  3603  	return m.pap.getACcountTypesForPAP()
  3604  }
  3605  
  3606  func (m *Market) BeginBlock(ctx context.Context) {}