code.vegaprotocol.io/vega@v0.79.0/core/execution/amm/engine.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 amm
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"sort"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/events"
    27  	"code.vegaprotocol.io/vega/core/execution/common"
    28  	"code.vegaprotocol.io/vega/core/idgeneration"
    29  	"code.vegaprotocol.io/vega/core/positions"
    30  	"code.vegaprotocol.io/vega/core/types"
    31  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    32  	"code.vegaprotocol.io/vega/libs/crypto"
    33  	"code.vegaprotocol.io/vega/libs/num"
    34  	"code.vegaprotocol.io/vega/logging"
    35  	v1 "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    36  
    37  	"golang.org/x/exp/maps"
    38  )
    39  
    40  var (
    41  	ErrNoPoolMatchingParty  = errors.New("no pool matching party")
    42  	ErrPartyAlreadyOwnAPool = func(market string) error {
    43  		return fmt.Errorf("party already own a pool for market %v", market)
    44  	}
    45  	ErrCommitmentTooLow          = errors.New("commitment amount too low")
    46  	ErrRebaseOrderDidNotTrade    = errors.New("rebase-order did not trade")
    47  	ErrRebaseTargetOutsideBounds = errors.New("rebase target outside bounds")
    48  )
    49  
    50  const (
    51  	V1 = "AMMv1"
    52  )
    53  
    54  //go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/execution/amm Collateral,Position
    55  
    56  type Collateral interface {
    57  	GetAssetQuantum(asset string) (num.Decimal, error)
    58  	GetAllParties() []string
    59  	GetPartyMarginAccount(market, party, asset string) (*types.Account, error)
    60  	GetPartyGeneralAccount(party, asset string) (*types.Account, error)
    61  	SubAccountUpdate(
    62  		ctx context.Context,
    63  		party, subAccount, asset, market string,
    64  		transferType types.TransferType,
    65  		amount *num.Uint,
    66  	) (*types.LedgerMovement, error)
    67  	SubAccountClosed(ctx context.Context, party, subAccount, asset, market string) ([]*types.LedgerMovement, error)
    68  	SubAccountRelease(
    69  		ctx context.Context,
    70  		party, subAccount, asset, market string, mevt events.MarketPosition,
    71  	) ([]*types.LedgerMovement, events.Margin, error)
    72  	CreatePartyAMMsSubAccounts(
    73  		ctx context.Context,
    74  		party, subAccount, asset, market string,
    75  	) (general *types.Account, margin *types.Account, err error)
    76  }
    77  
    78  type Broker interface {
    79  	Send(events.Event)
    80  }
    81  
    82  type Position interface {
    83  	GetPositionsByParty(ids ...string) []events.MarketPosition
    84  }
    85  
    86  type sqrtFn func(*num.Uint) num.Decimal
    87  
    88  // Sqrter calculates sqrt's of Uints and caches the results. We want this cache to be shared across all pools for a market.
    89  type Sqrter struct {
    90  	cache map[string]num.Decimal
    91  }
    92  
    93  func NewSqrter() *Sqrter {
    94  	return &Sqrter{cache: map[string]num.Decimal{}}
    95  }
    96  
    97  // sqrt calculates the square root of the uint and caches it.
    98  func (s *Sqrter) sqrt(u *num.Uint) num.Decimal {
    99  	if u.IsZero() {
   100  		return num.DecimalZero()
   101  	}
   102  
   103  	// caching was disabled here since it caused problems with snapshots (https://github.com/vegaprotocol/vega/issues/11523)
   104  	// and we changed tact to instead cache constant terms in calculations that *involve* sqrt's instead of the sqrt result
   105  	// directly. I'm leaving the ghost of this cache here incase we need to introduce it again, maybe as a LRU cache instead.
   106  	// if r, ok := s.cache[u.String()]; ok {
   107  	//	return r
   108  	// }
   109  
   110  	r := num.UintOne().Sqrt(u)
   111  
   112  	// s.cache[u.String()] = r
   113  	return r
   114  }
   115  
   116  type Engine struct {
   117  	log *logging.Logger
   118  
   119  	broker                Broker
   120  	marketActivityTracker *common.MarketActivityTracker
   121  
   122  	collateral Collateral
   123  	position   Position
   124  	parties    common.Parties
   125  
   126  	marketID string
   127  	assetID  string
   128  	idgen    *idgeneration.IDGenerator
   129  
   130  	// gets us from the price in the submission -> price in full asset dp
   131  	priceFactor    num.Decimal
   132  	positionFactor num.Decimal
   133  	oneTick        *num.Uint
   134  
   135  	// map of party -> pool
   136  	pools    map[string]*Pool
   137  	poolsCpy []*Pool
   138  
   139  	// sqrt calculator with cache
   140  	rooter *Sqrter
   141  
   142  	// a mapping of all amm-party-ids to the party owning them.
   143  	ammParties map[string]string
   144  
   145  	minCommitmentQuantum  *num.Uint
   146  	maxCalculationLevels  *num.Uint
   147  	allowedEmptyAMMLevels uint64
   148  }
   149  
   150  func New(
   151  	log *logging.Logger,
   152  	broker Broker,
   153  	collateral Collateral,
   154  	marketID string,
   155  	assetID string,
   156  	position Position,
   157  	priceFactor num.Decimal,
   158  	positionFactor num.Decimal,
   159  	marketActivityTracker *common.MarketActivityTracker,
   160  	parties common.Parties,
   161  	allowedEmptyAMMLevels uint64,
   162  ) *Engine {
   163  	oneTick, _ := num.UintFromDecimal(priceFactor)
   164  	return &Engine{
   165  		log:                   log,
   166  		broker:                broker,
   167  		collateral:            collateral,
   168  		position:              position,
   169  		marketID:              marketID,
   170  		assetID:               assetID,
   171  		marketActivityTracker: marketActivityTracker,
   172  		pools:                 map[string]*Pool{},
   173  		poolsCpy:              []*Pool{},
   174  		ammParties:            map[string]string{},
   175  		minCommitmentQuantum:  num.UintZero(),
   176  		rooter:                &Sqrter{cache: map[string]num.Decimal{}},
   177  		priceFactor:           priceFactor,
   178  		positionFactor:        positionFactor,
   179  		parties:               parties,
   180  		oneTick:               num.Max(num.UintOne(), oneTick),
   181  		allowedEmptyAMMLevels: allowedEmptyAMMLevels,
   182  	}
   183  }
   184  
   185  func NewFromProto(
   186  	log *logging.Logger,
   187  	broker Broker,
   188  	collateral Collateral,
   189  	marketID string,
   190  	assetID string,
   191  	position Position,
   192  	state *v1.AmmState,
   193  	priceFactor num.Decimal,
   194  	positionFactor num.Decimal,
   195  	marketActivityTracker *common.MarketActivityTracker,
   196  	parties common.Parties,
   197  	allowedEmptyAMMLevels uint64,
   198  ) (*Engine, error) {
   199  	e := New(log, broker, collateral, marketID, assetID, position, priceFactor, positionFactor, marketActivityTracker, parties, allowedEmptyAMMLevels)
   200  
   201  	for _, v := range state.AmmPartyIds {
   202  		e.ammParties[v.Key] = v.Value
   203  	}
   204  
   205  	for _, v := range state.Pools {
   206  		p, err := NewPoolFromProto(log, e.rooter.sqrt, e.collateral, e.position, v.Pool, v.Party, priceFactor, positionFactor)
   207  		if err != nil {
   208  			return e, err
   209  		}
   210  		e.add(p)
   211  	}
   212  
   213  	return e, nil
   214  }
   215  
   216  func (e *Engine) IntoProto() *v1.AmmState {
   217  	state := &v1.AmmState{
   218  		AmmPartyIds: make([]*v1.StringMapEntry, 0, len(e.ammParties)),
   219  		Pools:       make([]*v1.PoolMapEntry, 0, len(e.pools)),
   220  	}
   221  
   222  	for k, v := range e.ammParties {
   223  		state.AmmPartyIds = append(state.AmmPartyIds, &v1.StringMapEntry{
   224  			Key:   k,
   225  			Value: v,
   226  		})
   227  	}
   228  	sort.Slice(state.AmmPartyIds, func(i, j int) bool { return state.AmmPartyIds[i].Key < state.AmmPartyIds[j].Key })
   229  
   230  	for _, v := range e.poolsCpy {
   231  		state.Pools = append(state.Pools, &v1.PoolMapEntry{
   232  			Party: v.owner,
   233  			Pool:  v.IntoProto(),
   234  		})
   235  	}
   236  	return state
   237  }
   238  
   239  func (e *Engine) OnMinCommitmentQuantumUpdate(ctx context.Context, c *num.Uint) {
   240  	e.minCommitmentQuantum = c.Clone()
   241  }
   242  
   243  func (e *Engine) OnMaxCalculationLevelsUpdate(ctx context.Context, c *num.Uint) {
   244  	e.maxCalculationLevels = c.Clone()
   245  
   246  	for _, p := range e.poolsCpy {
   247  		p.maxCalculationLevels = e.maxCalculationLevels.Clone()
   248  	}
   249  }
   250  
   251  func (e *Engine) UpdateAllowedEmptyLevels(allowedEmptyLevels uint64) {
   252  	e.allowedEmptyAMMLevels = allowedEmptyLevels
   253  }
   254  
   255  // OnMTM is called whenever core does an MTM and is a signal that any pool's that are closing and have 0 position can be fully removed.
   256  func (e *Engine) OnMTM(ctx context.Context) {
   257  	rm := []string{}
   258  	for _, p := range e.poolsCpy {
   259  		if !p.closing() {
   260  			continue
   261  		}
   262  		if pos := p.getPosition(); pos != 0 {
   263  			continue
   264  		}
   265  
   266  		// pool is closing and has reached 0 position, we can cancel it now
   267  		if _, err := e.releaseSubAccounts(ctx, p, false); err != nil {
   268  			e.log.Error("unable to release subaccount balance", logging.Error(err))
   269  		}
   270  		p.status = types.AMMPoolStatusCancelled
   271  		rm = append(rm, p.owner)
   272  	}
   273  	for _, party := range rm {
   274  		e.remove(ctx, party)
   275  	}
   276  }
   277  
   278  func (e *Engine) OnTick(ctx context.Context, _ time.Time) {
   279  	// seed an id-generator to create IDs for any orders generated in this block
   280  	_, blockHash := vgcontext.TraceIDFromContext(ctx)
   281  	e.idgen = idgeneration.New(blockHash + crypto.HashStrToHex("amm-engine"+e.marketID))
   282  
   283  	// any pools that for some reason have zero balance in their accounts will get stopped
   284  	rm := []string{}
   285  	for _, p := range e.poolsCpy {
   286  		if p.getBalance().IsZero() {
   287  			p.status = types.AMMPoolStatusStopped
   288  			rm = append(rm, p.owner)
   289  		}
   290  	}
   291  	for _, party := range rm {
   292  		e.remove(ctx, party)
   293  	}
   294  }
   295  
   296  // RemoveDistressed checks if any of the closed out parties are AMM's and if so the AMM is stopped and removed.
   297  func (e *Engine) RemoveDistressed(ctx context.Context, closed []events.MarketPosition) {
   298  	for _, c := range closed {
   299  		owner, ok := e.ammParties[c.Party()]
   300  		if !ok {
   301  			continue
   302  		}
   303  		p, ok := e.pools[owner]
   304  		if !ok {
   305  			e.log.Panic("could not find pool for owner, not possible",
   306  				logging.String("owner", c.Party()),
   307  				logging.String("owner", owner),
   308  			)
   309  		}
   310  		p.status = types.AMMPoolStatusStopped
   311  		e.remove(ctx, owner)
   312  	}
   313  }
   314  
   315  // BestPricesAndVolumes returns the best bid/ask and their volumes across all the registered AMM's.
   316  func (e *Engine) BestPricesAndVolumes() (*num.Uint, uint64, *num.Uint, uint64) {
   317  	var bestBid, bestAsk *num.Uint
   318  	var bestBidVolume, bestAskVolume uint64
   319  
   320  	for _, pool := range e.poolsCpy {
   321  		var volume uint64
   322  		bid, volume := pool.BestPriceAndVolume(types.SideBuy)
   323  		if volume != 0 {
   324  			if bestBid == nil || bid.GT(bestBid) {
   325  				bestBid = bid
   326  				bestBidVolume = volume
   327  			} else if bid.EQ(bestBid) {
   328  				bestBidVolume += volume
   329  			}
   330  		}
   331  
   332  		ask, volume := pool.BestPriceAndVolume(types.SideSell)
   333  		if volume != 0 {
   334  			if bestAsk == nil || ask.LT(bestAsk) {
   335  				bestAsk = ask
   336  				bestAskVolume = volume
   337  			} else if ask.EQ(bestAsk) {
   338  				bestAskVolume += volume
   339  			}
   340  		}
   341  	}
   342  	return bestBid, bestBidVolume, bestAsk, bestAskVolume
   343  }
   344  
   345  // GetVolumeAtPrice returns the volumes across all registered AMM's that will uncross with with an order at the given price.
   346  // Calling this function with price 1000 and side == sell will return the buy orders that will uncross.
   347  func (e *Engine) GetVolumeAtPrice(price *num.Uint, side types.Side) uint64 {
   348  	vol := uint64(0)
   349  	for _, pool := range e.poolsCpy {
   350  		// get the pool's current price
   351  		best, ok := pool.BestPrice(types.OtherSide(side))
   352  		if !ok {
   353  			continue
   354  		}
   355  
   356  		// make sure price is in tradable range
   357  		if side == types.SideBuy && best.GT(price) {
   358  			continue
   359  		}
   360  
   361  		if side == types.SideSell && best.LT(price) {
   362  			continue
   363  		}
   364  
   365  		volume := pool.TradableVolumeForPrice(side, price)
   366  		vol += volume
   367  	}
   368  	return vol
   369  }
   370  
   371  func (e *Engine) submit(active []*Pool, agg *types.Order, inner, outer *num.Uint) []*types.Order {
   372  	if e.log.GetLevel() == logging.DebugLevel {
   373  		e.log.Debug("checking for volume between",
   374  			logging.String("inner", inner.String()),
   375  			logging.String("outer", outer.String()),
   376  		)
   377  	}
   378  
   379  	orders := []*types.Order{}
   380  	useActive := make([]*Pool, 0, len(active))
   381  	for _, p := range active {
   382  		p.setEphemeralPosition()
   383  
   384  		price, ok := p.BestPrice(types.OtherSide(agg.Side))
   385  		if !ok {
   386  			continue
   387  		}
   388  
   389  		if e.log.GetLevel() == logging.DebugLevel {
   390  			e.log.Debug("best price for pool",
   391  				logging.String("amm-party", p.AMMParty),
   392  				logging.String("best-price", price.String()),
   393  			)
   394  		}
   395  
   396  		if agg.Side == types.SideBuy {
   397  			if price.GT(outer) || (agg.Type != types.OrderTypeMarket && price.GT(agg.Price)) {
   398  				// either fair price is out of bounds, or is selling at higher than incoming buy
   399  				continue
   400  			}
   401  		}
   402  
   403  		if agg.Side == types.SideSell {
   404  			if price.LT(outer) || (agg.Type != types.OrderTypeMarket && price.LT(agg.Price)) {
   405  				// either fair price is out of bounds, or is buying at lower than incoming sell
   406  				continue
   407  			}
   408  		}
   409  		useActive = append(useActive, p)
   410  	}
   411  
   412  	// calculate the volume each pool has
   413  	var total uint64
   414  	volumes := []uint64{}
   415  	for _, p := range useActive {
   416  		volume := p.TradableVolumeForPrice(agg.Side, outer)
   417  		if e.log.GetLevel() == logging.DebugLevel {
   418  			e.log.Debug("volume available to trade",
   419  				logging.Uint64("volume", volume),
   420  				logging.String("amm-party", p.AMMParty),
   421  			)
   422  		}
   423  
   424  		volumes = append(volumes, volume)
   425  		total += volume
   426  	}
   427  
   428  	// if the pools consume the whole incoming order's volume, share it out pro-rata
   429  	if agg.Remaining < total {
   430  		maxVolumes := make([]uint64, 0, len(volumes))
   431  		// copy the available volumes for rounding.
   432  		maxVolumes = append(maxVolumes, volumes...)
   433  		var retotal uint64
   434  		for i := range volumes {
   435  			volumes[i] = agg.Remaining * volumes[i] / total
   436  			retotal += volumes[i]
   437  		}
   438  
   439  		// any lost crumbs due to integer division is given to the pools that can accommodate it.
   440  		if d := agg.Remaining - retotal; d != 0 {
   441  			for i, v := range volumes {
   442  				if delta := maxVolumes[i] - v; delta != 0 {
   443  					if delta >= d {
   444  						volumes[i] += d
   445  						break
   446  					}
   447  					volumes[i] += delta
   448  					d -= delta
   449  				}
   450  			}
   451  		}
   452  	}
   453  
   454  	// now generate offbook orders
   455  	for i, p := range useActive {
   456  		volume := volumes[i]
   457  		if volume == 0 {
   458  			continue
   459  		}
   460  
   461  		// calculate the price the pool wil give for the trading volume
   462  		price := p.PriceForVolume(volume, agg.Side)
   463  
   464  		if e.log.IsDebug() {
   465  			e.log.Debug("generated order at price",
   466  				logging.String("price", price.String()),
   467  				logging.Uint64("volume", volume),
   468  				logging.String("id", p.ID),
   469  				logging.String("side", types.OtherSide(agg.Side).String()),
   470  			)
   471  		}
   472  
   473  		// construct an order
   474  		o := p.makeOrder(volume, price, types.OtherSide(agg.Side), e.idgen)
   475  
   476  		// fill in extra details
   477  		o.CreatedAt = agg.CreatedAt
   478  
   479  		orders = append(orders, o)
   480  		p.updateEphemeralPosition(o)
   481  
   482  		agg.Remaining -= volume
   483  	}
   484  
   485  	return orders
   486  }
   487  
   488  // partition takes the given price range and returns which pools have volume in that region, and
   489  // divides that range into sub-levels where AMM boundaries end. Note that `outer` can be nil for the case
   490  // where the incoming order is a market order (so we have no bound on the price), and we've already consumed
   491  // all volume on the orderbook.
   492  func (e *Engine) partition(agg *types.Order, inner, outer *num.Uint) ([]*Pool, []*num.Uint) {
   493  	active := []*Pool{}
   494  	bounds := map[string]*num.Uint{}
   495  
   496  	// cap outer to incoming order price
   497  	if agg.Type != types.OrderTypeMarket {
   498  		switch {
   499  		case outer == nil:
   500  			outer = agg.Price.Clone()
   501  		case agg.Side == types.SideSell && agg.Price.GT(outer):
   502  			outer = agg.Price.Clone()
   503  		case agg.Side == types.SideBuy && agg.Price.LT(outer):
   504  			outer = agg.Price.Clone()
   505  		}
   506  	}
   507  
   508  	if inner == nil {
   509  		// if inner is given as nil it means the matching engine is trading up to its first price level
   510  		// and so has no lower bound on the range. So we'll calculate one using best price of all pools
   511  		// note that if the incoming order is a buy the price range we need to evaluate is from
   512  		// fair-price -> best-ask -> outer, so we need to step one back. But then if we use fair-price exactly we
   513  		// risk hitting numerical problems and given this is just to exclude AMM's completely out of range we
   514  		// can be a bit looser and so step back again so that we evaluate from best-buy -> best-ask -> outer.
   515  		buy, _, ask, _ := e.BestPricesAndVolumes()
   516  		two := num.UintZero().AddSum(e.oneTick, e.oneTick)
   517  		if agg.Side == types.SideBuy && ask != nil {
   518  			inner = num.UintZero().Sub(ask, two)
   519  		}
   520  		if agg.Side == types.SideSell && buy != nil {
   521  			inner = num.UintZero().Add(buy, two)
   522  		}
   523  	}
   524  
   525  	// switch so that inner < outer to make it easier to reason with
   526  	if agg.Side == types.SideSell {
   527  		inner, outer = outer, inner
   528  	}
   529  
   530  	// if inner and outer are equal then we are wanting to trade with AMMs *only at* this given price
   531  	// this can happen quite easily during auction uncrossing where two AMMs have bases offset by 2
   532  	// and the crossed region is simply a point and not an interval. To be able to query the tradable
   533  	// volume of an AMM at a point, we need to first convert it to an interval by stepping one tick away first.
   534  	// This is because to get the BUY volume an AMM has at price P, we need to calculate the difference
   535  	// in its position between prices P -> P + 1. For SELL volume its the other way around and we
   536  	// need the difference in position from P - 1 -> P.
   537  	if inner != nil && outer != nil && inner.EQ(outer) {
   538  		if agg.Side == types.SideSell {
   539  			outer = num.UintZero().Add(outer, e.oneTick)
   540  		} else {
   541  			inner = num.UintZero().Sub(inner, e.oneTick)
   542  		}
   543  	}
   544  
   545  	if inner != nil {
   546  		bounds[inner.String()] = inner.Clone()
   547  	}
   548  	if outer != nil {
   549  		bounds[outer.String()] = outer.Clone()
   550  	}
   551  
   552  	for _, p := range e.poolsCpy {
   553  		// not active in range if it cannot trade
   554  		if !p.canTrade(agg.Side) {
   555  			continue
   556  		}
   557  
   558  		// stop early trying to trade with itself, can happens during auction uncrossing
   559  		if agg.Party == p.AMMParty {
   560  			continue
   561  		}
   562  
   563  		// not active in range if its the pool's curves are wholly outside of [inner, outer]
   564  		if (inner != nil && p.upper.high.LT(inner)) || (outer != nil && p.lower.low.GT(outer)) {
   565  			continue
   566  		}
   567  
   568  		// pool is active in range add it to the slice
   569  		active = append(active, p)
   570  
   571  		// we hit a discontinuity where an AMM's two curves meet if we try to trade over its base-price
   572  		// so we partition the inner/outer price range at the base price so that we instead trade across it
   573  		// in two steps.
   574  		boundary := p.upper.low
   575  		if inner != nil && outer != nil {
   576  			if boundary.LT(outer) && boundary.GT(inner) {
   577  				bounds[boundary.String()] = boundary.Clone()
   578  			}
   579  		} else if outer == nil && boundary.GT(inner) {
   580  			bounds[boundary.String()] = boundary.Clone()
   581  		} else if inner == nil && boundary.LT(outer) {
   582  			bounds[boundary.String()] = boundary.Clone()
   583  		}
   584  
   585  		// if a pool's upper or lower boundary exists within (inner, outer) then we consider that a sub-level
   586  		boundary = p.upper.high
   587  		if outer == nil || boundary.LT(outer) {
   588  			bounds[boundary.String()] = boundary.Clone()
   589  		}
   590  
   591  		boundary = p.lower.low
   592  		if inner == nil || boundary.GT(inner) {
   593  			bounds[boundary.String()] = boundary.Clone()
   594  		}
   595  	}
   596  
   597  	// now sort the sub-levels, if the incoming order is a buy we want them ordered ascending so we consider prices in this order:
   598  	// 2000 -> 2100 -> 2200
   599  	//
   600  	// and if its a sell we want them descending so we consider them like:
   601  	// 2000 -> 1900 -> 1800
   602  	levels := maps.Values(bounds)
   603  	sort.Slice(levels,
   604  		func(i, j int) bool {
   605  			if agg.Side == types.SideSell {
   606  				return levels[i].GT(levels[j])
   607  			}
   608  			return levels[i].LT(levels[j])
   609  		},
   610  	)
   611  	return active, levels
   612  }
   613  
   614  // SubmitOrder takes an aggressive order and generates matching orders with the registered AMMs such that
   615  // volume is only taken in the interval (inner, outer) where inner and outer are price-levels on the orderbook.
   616  // For example if agg is a buy order inner < outer, and if its a sell outer < inner.
   617  func (e *Engine) SubmitOrder(agg *types.Order, inner, outer *num.Uint) []*types.Order {
   618  	if len(e.pools) == 0 {
   619  		return nil
   620  	}
   621  
   622  	if e.log.GetLevel() == logging.DebugLevel {
   623  		e.log.Debug("looking for match with order",
   624  			logging.Int("n-pools", len(e.pools)),
   625  			logging.Order(agg),
   626  		)
   627  	}
   628  
   629  	// parition the given range into levels where AMM boundaries end
   630  	agg = agg.Clone()
   631  	active, levels := e.partition(agg, inner, outer)
   632  
   633  	// submit orders to active pool's between each price level created by any of their high/low boundaries
   634  	orders := []*types.Order{}
   635  	for i := 0; i < len(levels)-1; i++ {
   636  		o := e.submit(active, agg, levels[i], levels[i+1])
   637  		orders = append(orders, o...)
   638  
   639  		if agg.Remaining == 0 {
   640  			break
   641  		}
   642  	}
   643  
   644  	return orders
   645  }
   646  
   647  // NotifyFinished is called when the matching engine has finished matching an order and is returning it to
   648  // the market for processing.
   649  func (e *Engine) NotifyFinished() {
   650  	for _, p := range e.poolsCpy {
   651  		p.clearEphemeralPosition()
   652  	}
   653  }
   654  
   655  // Create takes the definition of an AMM and returns it. It is not considered a participating AMM until Confirm as been called with it.
   656  func (e *Engine) Create(
   657  	ctx context.Context,
   658  	submit *types.SubmitAMM,
   659  	deterministicID string,
   660  	riskFactors *types.RiskFactor,
   661  	scalingFactors *types.ScalingFactors,
   662  	slippage num.Decimal,
   663  ) (*Pool, error) {
   664  	idgen := idgeneration.New(deterministicID)
   665  	poolID := idgen.NextID()
   666  
   667  	subAccount := DeriveAMMParty(submit.Party, submit.MarketID, V1, 0)
   668  	_, ok := e.pools[submit.Party]
   669  	if ok {
   670  		return nil, ErrPartyAlreadyOwnAPool(e.marketID)
   671  	}
   672  
   673  	if err := e.ensureCommitmentAmount(ctx, submit.Party, subAccount, submit.CommitmentAmount); err != nil {
   674  		return nil, err
   675  	}
   676  
   677  	_, _, err := e.collateral.CreatePartyAMMsSubAccounts(ctx, submit.Party, subAccount, e.assetID, submit.MarketID)
   678  	if err != nil {
   679  		return nil, err
   680  	}
   681  
   682  	pool, err := NewPool(
   683  		e.log,
   684  		poolID,
   685  		subAccount,
   686  		e.assetID,
   687  		submit,
   688  		e.rooter.sqrt,
   689  		e.collateral,
   690  		e.position,
   691  		riskFactors,
   692  		scalingFactors,
   693  		slippage,
   694  		e.priceFactor,
   695  		e.positionFactor,
   696  		e.maxCalculationLevels,
   697  		e.allowedEmptyAMMLevels,
   698  		submit.SlippageTolerance,
   699  		submit.MinimumPriceChangeTrigger,
   700  	)
   701  	if err != nil {
   702  		return nil, err
   703  	}
   704  
   705  	// sanity check, a *new* AMM should not already have a position. If it does it means that the party
   706  	// previously had an AMM but it was stopped/cancelled while still holding a position which should not happen.
   707  	// It should have either handed its position over to the liquidation engine, or be in reduce-only mode
   708  	// and only be removed when its position is 0.
   709  	if pool.getPosition() != 0 {
   710  		e.log.Panic("AMM has position before existing")
   711  	}
   712  
   713  	e.log.Debug("AMM created",
   714  		logging.String("owner", submit.Party),
   715  		logging.String("poolID", pool.ID),
   716  		logging.String("marketID", e.marketID),
   717  	)
   718  	return pool, nil
   719  }
   720  
   721  // Confirm takes an AMM that was created earlier and now commits it to the engine as a functioning pool.
   722  func (e *Engine) Confirm(
   723  	ctx context.Context,
   724  	pool *Pool,
   725  ) {
   726  	e.log.Debug("AMM confirmed",
   727  		logging.String("owner", pool.owner),
   728  		logging.String("marketID", e.marketID),
   729  		logging.String("poolID", pool.ID),
   730  	)
   731  
   732  	pool.maxCalculationLevels = e.maxCalculationLevels
   733  
   734  	e.add(pool)
   735  	e.sendUpdate(ctx, pool)
   736  	e.parties.AssignDeriveKey(ctx, types.PartyID(pool.owner), pool.AMMParty)
   737  }
   738  
   739  // Amend takes the details of an amendment to an AMM and returns a copy of that pool with the updated curves along with the current pool.
   740  // The changes are not taken place in the AMM engine until Confirm is called on the updated pool.
   741  func (e *Engine) Amend(
   742  	ctx context.Context,
   743  	amend *types.AmendAMM,
   744  	riskFactors *types.RiskFactor,
   745  	scalingFactors *types.ScalingFactors,
   746  	slippage num.Decimal,
   747  ) (*Pool, *Pool, error) {
   748  	pool, ok := e.pools[amend.Party]
   749  	if !ok {
   750  		return nil, nil, ErrNoPoolMatchingParty
   751  	}
   752  
   753  	if amend.CommitmentAmount != nil {
   754  		if err := e.ensureCommitmentAmount(ctx, amend.Party, pool.AMMParty, amend.CommitmentAmount); err != nil {
   755  			return nil, nil, err
   756  		}
   757  	}
   758  
   759  	updated, err := pool.Update(amend, riskFactors, scalingFactors, slippage, e.allowedEmptyAMMLevels)
   760  	if err != nil {
   761  		return nil, nil, err
   762  	}
   763  
   764  	// we need to remove the existing pool from the engine so that when calculating rebasing orders we do not
   765  	// trade with ourselves.
   766  	e.remove(ctx, amend.Party)
   767  
   768  	e.log.Debug("AMM amended",
   769  		logging.String("owner", amend.Party),
   770  		logging.String("marketID", e.marketID),
   771  		logging.String("poolID", pool.ID),
   772  	)
   773  	return updated, pool, nil
   774  }
   775  
   776  // GetDataSourcedAMMs returns any AMM's whose base price is determined by the given data source ID.
   777  func (e *Engine) GetDataSourcedAMMs(dataSourceID string) []*Pool {
   778  	pools := []*Pool{}
   779  	for _, p := range e.poolsCpy {
   780  		if p.Parameters.DataSourceID == nil {
   781  			continue
   782  		}
   783  
   784  		if *p.Parameters.DataSourceID != dataSourceID {
   785  			continue
   786  		}
   787  
   788  		pools = append(pools, p)
   789  	}
   790  	return pools
   791  }
   792  
   793  func (e *Engine) CancelAMM(
   794  	ctx context.Context,
   795  	cancel *types.CancelAMM,
   796  ) (events.Margin, error) {
   797  	pool, ok := e.pools[cancel.Party]
   798  	if !ok {
   799  		return nil, ErrNoPoolMatchingParty
   800  	}
   801  
   802  	if cancel.Method == types.AMMCancellationMethodReduceOnly {
   803  		// pool will now only accept trades that will reduce its position
   804  		pool.status = types.AMMPoolStatusReduceOnly
   805  		e.sendUpdate(ctx, pool)
   806  		return nil, nil
   807  	}
   808  
   809  	// either pool has no position or owner wants out right now, so release general balance and
   810  	// get ready for a closeout.
   811  	closeout, err := e.releaseSubAccounts(ctx, pool, false)
   812  	if err != nil {
   813  		return nil, err
   814  	}
   815  
   816  	pool.status = types.AMMPoolStatusCancelled
   817  	e.remove(ctx, cancel.Party)
   818  	e.log.Debug("AMM cancelled",
   819  		logging.String("owner", cancel.Party),
   820  		logging.String("poolID", pool.ID),
   821  		logging.String("marketID", e.marketID),
   822  	)
   823  	return closeout, nil
   824  }
   825  
   826  func (e *Engine) StopPool(
   827  	ctx context.Context,
   828  	key string,
   829  ) error {
   830  	party, ok := e.ammParties[key]
   831  	if !ok {
   832  		return ErrNoPoolMatchingParty
   833  	}
   834  	e.remove(ctx, party)
   835  	return nil
   836  }
   837  
   838  // MarketClosing stops all AMM's and returns subaccount balances back to the owning party.
   839  func (e *Engine) MarketClosing(ctx context.Context) error {
   840  	for _, p := range e.poolsCpy {
   841  		if _, err := e.releaseSubAccounts(ctx, p, true); err != nil {
   842  			return err
   843  		}
   844  		p.status = types.AMMPoolStatusStopped
   845  		e.sendUpdate(ctx, p)
   846  		e.marketActivityTracker.RemoveAMMParty(e.assetID, e.marketID, p.AMMParty)
   847  	}
   848  
   849  	e.pools = nil
   850  	e.poolsCpy = nil
   851  	e.ammParties = nil
   852  	return nil
   853  }
   854  
   855  func (e *Engine) sendUpdate(ctx context.Context, pool *Pool) {
   856  	e.broker.Send(
   857  		events.NewAMMPoolEvent(
   858  			ctx, pool.owner, e.marketID, pool.AMMParty, pool.ID,
   859  			pool.Commitment, pool.Parameters,
   860  			pool.status, types.AMMStatusReasonUnspecified,
   861  			pool.ProposedFee,
   862  			&events.AMMCurve{
   863  				VirtualLiquidity:    pool.lower.l,
   864  				TheoreticalPosition: pool.lower.pv,
   865  			},
   866  			&events.AMMCurve{
   867  				VirtualLiquidity:    pool.upper.l,
   868  				TheoreticalPosition: pool.upper.pv,
   869  			},
   870  			pool.MinimumPriceChangeTrigger,
   871  		),
   872  	)
   873  }
   874  
   875  func (e *Engine) ensureCommitmentAmount(
   876  	_ context.Context,
   877  	party string,
   878  	subAccount string,
   879  	commitmentAmount *num.Uint,
   880  ) error {
   881  	quantum, _ := e.collateral.GetAssetQuantum(e.assetID)
   882  	quantumCommitment := commitmentAmount.ToDecimal().Div(quantum)
   883  
   884  	if quantumCommitment.LessThan(e.minCommitmentQuantum.ToDecimal()) {
   885  		return ErrCommitmentTooLow
   886  	}
   887  
   888  	total := num.UintZero()
   889  
   890  	// check they have enough in their accounts, sub-margin + sub-general + general >= commitment
   891  	if a, err := e.collateral.GetPartyMarginAccount(e.marketID, subAccount, e.assetID); err == nil {
   892  		total.Add(total, a.Balance)
   893  	}
   894  
   895  	if a, err := e.collateral.GetPartyGeneralAccount(subAccount, e.assetID); err == nil {
   896  		total.Add(total, a.Balance)
   897  	}
   898  
   899  	if a, err := e.collateral.GetPartyGeneralAccount(party, e.assetID); err == nil {
   900  		total.Add(total, a.Balance)
   901  	}
   902  
   903  	if total.LT(commitmentAmount) {
   904  		return fmt.Errorf("not enough collateral in general account")
   905  	}
   906  
   907  	return nil
   908  }
   909  
   910  // releaseSubAccountGeneralBalance returns the full balance of the sub-accounts general account back to the
   911  // owner of the pool.
   912  func (e *Engine) releaseSubAccounts(ctx context.Context, pool *Pool, mktClose bool) (events.Margin, error) {
   913  	if mktClose {
   914  		ledgerMovements, err := e.collateral.SubAccountClosed(ctx, pool.owner, pool.AMMParty, pool.asset, pool.market)
   915  		if err != nil {
   916  			return nil, err
   917  		}
   918  		e.broker.Send(events.NewLedgerMovements(ctx, ledgerMovements))
   919  		return nil, nil
   920  	}
   921  	var pos events.MarketPosition
   922  	if pp := e.position.GetPositionsByParty(pool.AMMParty); len(pp) > 0 {
   923  		pos = pp[0]
   924  	} else {
   925  		// if a pool is cancelled right after creation it won't have a position yet so we just make an empty one to give
   926  		// to collateral
   927  		pos = positions.NewMarketPosition(pool.AMMParty)
   928  	}
   929  
   930  	ledgerMovements, closeout, err := e.collateral.SubAccountRelease(ctx, pool.owner, pool.AMMParty, pool.asset, pool.market, pos)
   931  	if err != nil {
   932  		return nil, err
   933  	}
   934  
   935  	e.broker.Send(events.NewLedgerMovements(
   936  		ctx, ledgerMovements))
   937  	return closeout, nil
   938  }
   939  
   940  func (e *Engine) UpdateSubAccountBalance(
   941  	ctx context.Context,
   942  	party, subAccount string,
   943  	newCommitment *num.Uint,
   944  ) (*num.Uint, error) {
   945  	// first we get the current balance of both the margin, and general subAccount
   946  	subMargin, err := e.collateral.GetPartyMarginAccount(
   947  		e.marketID, subAccount, e.assetID)
   948  	if err != nil {
   949  		// by that point the account must exist
   950  		e.log.Panic("no sub margin account", logging.Error(err))
   951  	}
   952  	subGeneral, err := e.collateral.GetPartyGeneralAccount(
   953  		subAccount, e.assetID)
   954  	if err != nil {
   955  		// by that point the account must exist
   956  		e.log.Panic("no sub general account", logging.Error(err))
   957  	}
   958  
   959  	var (
   960  		currentCommitment = num.Sum(subMargin.Balance, subGeneral.Balance)
   961  		transferType      types.TransferType
   962  		actualAmount      = num.UintZero()
   963  	)
   964  
   965  	if currentCommitment.LT(newCommitment) {
   966  		transferType = types.TransferTypeAMMLow
   967  		actualAmount.Sub(newCommitment, currentCommitment)
   968  	} else if currentCommitment.GT(newCommitment) {
   969  		transferType = types.TransferTypeAMMHigh
   970  		actualAmount.Sub(currentCommitment, newCommitment)
   971  	} else {
   972  		// nothing to do
   973  		return currentCommitment, nil
   974  	}
   975  
   976  	ledgerMovements, err := e.collateral.SubAccountUpdate(
   977  		ctx, party, subAccount, e.assetID,
   978  		e.marketID, transferType, actualAmount,
   979  	)
   980  	if err != nil {
   981  		return nil, err
   982  	}
   983  
   984  	e.broker.Send(events.NewLedgerMovements(
   985  		ctx, []*types.LedgerMovement{ledgerMovements}))
   986  
   987  	return currentCommitment, nil
   988  }
   989  
   990  // OrderbookShape expands all registered AMM's into orders between the given prices. If `ammParty` is supplied then just the pool
   991  // with that party id is expanded.
   992  func (e *Engine) OrderbookShape(st, nd *num.Uint, ammParty *string) []*types.OrderbookShapeResult {
   993  	if ammParty == nil {
   994  		// no party give, expand all registered
   995  		res := make([]*types.OrderbookShapeResult, 0, len(e.poolsCpy))
   996  		for _, p := range e.poolsCpy {
   997  			res = append(res, p.OrderbookShape(st, nd, e.idgen))
   998  		}
   999  		return res
  1000  	}
  1001  
  1002  	// asked to expand just one AMM, lets find it, first amm-party -> owning party
  1003  	owner, ok := e.ammParties[*ammParty]
  1004  	if !ok {
  1005  		return nil
  1006  	}
  1007  
  1008  	// now owning party -> pool
  1009  	p, ok := e.pools[owner]
  1010  	if !ok {
  1011  		return nil
  1012  	}
  1013  
  1014  	// expand it
  1015  	return []*types.OrderbookShapeResult{p.OrderbookShape(st, nd, e.idgen)}
  1016  }
  1017  
  1018  func (e *Engine) GetAMMPoolsBySubAccount() map[string]common.AMMPool {
  1019  	ret := make(map[string]common.AMMPool, len(e.pools))
  1020  	for _, v := range e.pools {
  1021  		ret[v.AMMParty] = v
  1022  	}
  1023  	return ret
  1024  }
  1025  
  1026  func (e *Engine) GetAllSubAccounts() []string {
  1027  	ret := make([]string, 0, len(e.ammParties))
  1028  	for _, subAccount := range e.ammParties {
  1029  		ret = append(ret, subAccount)
  1030  	}
  1031  	return ret
  1032  }
  1033  
  1034  // GetAMMParty returns the AMM's key given the owners key.
  1035  func (e *Engine) GetAMMParty(party string) (string, error) {
  1036  	if p, ok := e.pools[party]; ok {
  1037  		return p.AMMParty, nil
  1038  	}
  1039  	return "", ErrNoPoolMatchingParty
  1040  }
  1041  
  1042  // IsAMMPartyID returns whether the given key is the key of AMM registered with the engine.
  1043  func (e *Engine) IsAMMPartyID(key string) bool {
  1044  	_, yes := e.ammParties[key]
  1045  	return yes
  1046  }
  1047  
  1048  func (e *Engine) add(p *Pool) {
  1049  	e.pools[p.owner] = p
  1050  	e.poolsCpy = append(e.poolsCpy, p)
  1051  	e.ammParties[p.AMMParty] = p.owner
  1052  	e.marketActivityTracker.AddAMMSubAccount(e.assetID, e.marketID, p.AMMParty)
  1053  }
  1054  
  1055  func (e *Engine) remove(ctx context.Context, party string) {
  1056  	for i := range e.poolsCpy {
  1057  		if e.poolsCpy[i].owner == party {
  1058  			e.poolsCpy = append(e.poolsCpy[:i], e.poolsCpy[i+1:]...)
  1059  			break
  1060  		}
  1061  	}
  1062  
  1063  	pool := e.pools[party]
  1064  	delete(e.pools, party)
  1065  	delete(e.ammParties, pool.AMMParty)
  1066  	e.sendUpdate(ctx, pool)
  1067  	e.marketActivityTracker.RemoveAMMParty(e.assetID, e.marketID, pool.AMMParty)
  1068  }
  1069  
  1070  func DeriveAMMParty(
  1071  	party, market, version string,
  1072  	index uint64,
  1073  ) string {
  1074  	hash := crypto.Hash([]byte(fmt.Sprintf("%v%v%v%v", version, market, party, index)))
  1075  	return hex.EncodeToString(hash)
  1076  }