code.vegaprotocol.io/vega@v0.79.0/core/matching/side.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 matching
    17  
    18  import (
    19  	"encoding/binary"
    20  	"fmt"
    21  	"sort"
    22  
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/crypto"
    25  	"code.vegaprotocol.io/vega/libs/num"
    26  	"code.vegaprotocol.io/vega/logging"
    27  
    28  	"github.com/pkg/errors"
    29  )
    30  
    31  // ErrPriceNotFound signals that a price was not found on the book side.
    32  var ErrPriceNotFound = errors.New("price-volume pair not found")
    33  
    34  // OrderBookSide represent a side of the book, either Sell or Buy.
    35  type OrderBookSide struct {
    36  	side    types.Side
    37  	log     *logging.Logger
    38  	levels  []*PriceLevel
    39  	offbook OffbookSource
    40  }
    41  
    42  func (s *OrderBookSide) Hash() []byte {
    43  	// 32 num.Uint.Bytes() for price + 8 for volume
    44  	output := make([]byte, len(s.levels)*40)
    45  	var i int
    46  	for _, l := range s.levels {
    47  		// Data is already coming as big endian out of
    48  		// Uint.Bytes()
    49  		price := l.price.Bytes()
    50  		copy(output[i:], price[:])
    51  		i += 32
    52  		binary.BigEndian.PutUint64(output[i:], l.volume)
    53  		i += 8
    54  	}
    55  	return crypto.Hash(output)
    56  }
    57  
    58  func (s *OrderBookSide) cleanup() {
    59  	s.levels = nil
    60  }
    61  
    62  // When we leave an auction we need to remove any orders marked as GFA.
    63  func (s *OrderBookSide) getOrdersToCancel(auction bool) []*types.Order {
    64  	ordersToCancel := make([]*types.Order, 0)
    65  	for _, pricelevel := range s.levels {
    66  		for _, order := range pricelevel.orders {
    67  			// Find orders to cancel
    68  			if (order.TimeInForce == types.OrderTimeInForceGFA && !auction) ||
    69  				(order.TimeInForce == types.OrderTimeInForceGFN && auction) {
    70  				// Save order to send back to client
    71  				ordersToCancel = append(ordersToCancel, order)
    72  			}
    73  		}
    74  	}
    75  	return ordersToCancel
    76  }
    77  
    78  func (s *OrderBookSide) addOrder(o *types.Order) {
    79  	// update the price-volume map
    80  	s.getPriceLevel(o.Price).addOrder(o)
    81  }
    82  
    83  // BestPriceAndVolume returns the top of book price and volume
    84  // returns an error if the book is empty.
    85  func (s *OrderBookSide) BestPriceAndVolume() (*num.Uint, uint64, error) {
    86  	if len(s.levels) <= 0 {
    87  		return num.UintZero(), 0, errors.New("no orders on the book")
    88  	}
    89  	last := len(s.levels) - 1
    90  	return s.levels[last].price.Clone(), s.levels[last].volume, nil
    91  }
    92  
    93  // BestStaticPrice returns the top of book price for non pegged orders
    94  // We do not keep count of the volume which makes this slightly quicker
    95  // returns an error if the book is empty.
    96  func (s *OrderBookSide) BestStaticPrice() (*num.Uint, error) {
    97  	if len(s.levels) <= 0 {
    98  		return num.UintZero(), errors.New("no orders on the book")
    99  	}
   100  
   101  	for i := len(s.levels) - 1; i >= 0; i-- {
   102  		pricelevel := s.levels[i]
   103  		for _, order := range pricelevel.orders {
   104  			if order.PeggedOrder == nil {
   105  				return pricelevel.price.Clone(), nil
   106  			}
   107  		}
   108  	}
   109  	return num.UintZero(), errors.New("no non pegged orders found on the book")
   110  }
   111  
   112  // BestStaticPriceAndVolume returns the top of book price for non pegged orders
   113  // returns an error if the book is empty.
   114  func (s *OrderBookSide) BestStaticPriceAndVolume() (*num.Uint, uint64, error) {
   115  	if len(s.levels) <= 0 {
   116  		return num.UintZero(), 0, errors.New("no orders on the book")
   117  	}
   118  
   119  	var (
   120  		bestPrice  = num.UintZero()
   121  		bestVolume uint64
   122  	)
   123  	for i := len(s.levels) - 1; i >= 0; i-- {
   124  		pricelevel := s.levels[i]
   125  		for _, order := range pricelevel.orders {
   126  			if order.PeggedOrder == nil {
   127  				bestPrice = pricelevel.price
   128  				bestVolume += order.Remaining
   129  			}
   130  		}
   131  		// If we found a price, return it
   132  		if bestPrice.GT(num.UintZero()) {
   133  			return bestPrice.Clone(), bestVolume, nil
   134  		}
   135  	}
   136  	return num.UintZero(), 0, errors.New("no non pegged orders found on the book")
   137  }
   138  
   139  func (s *OrderBookSide) amendIcebergOrder(amendOrder *types.Order, oldOrder *types.Order, priceLevelIndex int, orderIndex int) (int64, error) {
   140  	if amendOrder.Remaining > oldOrder.Remaining {
   141  		// iceberg amend should never increase the visible remaining
   142  		return 0, types.ErrOrderAmendFailure
   143  	}
   144  
   145  	// set the new order in the level
   146  	s.levels[priceLevelIndex].orders[orderIndex] = amendOrder
   147  
   148  	// iceberg orders are a little different because they can be increased or decreased in size but
   149  	// amended in place. This is because on increase only the reserve amount it changed.
   150  	oldReserved := oldOrder.IcebergOrder.ReservedRemaining
   151  	amendReserved := amendOrder.IcebergOrder.ReservedRemaining
   152  	if amendReserved > oldReserved {
   153  		// only increased volume diff is easy
   154  		inc := amendReserved - oldReserved
   155  		s.levels[priceLevelIndex].volume += inc
   156  		return int64(inc), nil
   157  	}
   158  
   159  	if amendReserved < oldReserved {
   160  		dec := oldOrder.Remaining - amendOrder.Remaining
   161  		dec += oldReserved - amendReserved
   162  		s.levels[priceLevelIndex].reduceVolume(dec)
   163  		return -int64(dec), nil
   164  	}
   165  
   166  	// this is the case where we have an iceberg with no reserve, and reducing its visible peak
   167  	if oldOrder.Remaining < amendOrder.Remaining {
   168  		panic("we should not be increasing iceberg visble size in-place")
   169  	}
   170  	return -int64(oldOrder.Remaining - amendOrder.Remaining), nil
   171  }
   172  
   173  func (s *OrderBookSide) amendOrder(orderAmend *types.Order) (int64, error) {
   174  	priceLevelIndex := -1
   175  	orderIndex := -1
   176  	var oldOrder *types.Order
   177  
   178  	for idx, priceLevel := range s.levels {
   179  		if priceLevel.price.EQ(orderAmend.Price) {
   180  			priceLevelIndex = idx
   181  			for j, order := range priceLevel.orders {
   182  				if order.ID == orderAmend.ID {
   183  					orderIndex = j
   184  					oldOrder = order
   185  					break
   186  				}
   187  			}
   188  			break
   189  		}
   190  	}
   191  
   192  	if oldOrder == nil || priceLevelIndex == -1 || orderIndex == -1 {
   193  		return 0, types.ErrOrderNotFound
   194  	}
   195  
   196  	if oldOrder.Party != orderAmend.Party {
   197  		return 0, types.ErrOrderAmendFailure
   198  	}
   199  
   200  	if oldOrder.Reference != orderAmend.Reference {
   201  		return 0, types.ErrOrderAmendFailure
   202  	}
   203  
   204  	if oldOrder.IcebergOrder != nil {
   205  		return s.amendIcebergOrder(orderAmend, oldOrder, priceLevelIndex, orderIndex)
   206  	}
   207  
   208  	if oldOrder.Size < orderAmend.Size &&
   209  		oldOrder.Remaining < orderAmend.Size {
   210  		return 0, types.ErrOrderAmendFailure
   211  	}
   212  
   213  	reduceBy := oldOrder.Remaining - orderAmend.Remaining
   214  	s.levels[priceLevelIndex].orders[orderIndex] = orderAmend
   215  	s.levels[priceLevelIndex].reduceVolume(reduceBy)
   216  	return -int64(reduceBy), nil
   217  }
   218  
   219  // ExtractOrders extracts the orders from the top of the book until the volume amount is hit,
   220  // if removeOrders is set to True then the relevant orders also get removed.
   221  func (s *OrderBookSide) ExtractOrders(price *num.Uint, volume uint64, removeOrders bool) []*types.Order {
   222  	extractedOrders := []*types.Order{}
   223  	if volume == 0 {
   224  		return extractedOrders
   225  	}
   226  
   227  	var (
   228  		totalVolume uint64
   229  		checkPrice  func(*num.Uint) bool
   230  	)
   231  	if s.side == types.SideBuy {
   232  		checkPrice = func(orderPrice *num.Uint) bool { return orderPrice.GTE(price) }
   233  	} else {
   234  		checkPrice = func(orderPrice *num.Uint) bool { return orderPrice.LTE(price) }
   235  	}
   236  
   237  	for i := len(s.levels) - 1; i >= 0; i-- {
   238  		pricelevel := s.levels[i]
   239  		var toRemove int
   240  		for _, order := range pricelevel.orders {
   241  			// Check the price is good and the total volume will not be exceeded
   242  			if checkPrice(order.Price) && totalVolume+order.TrueRemaining() <= volume {
   243  				// Remove this order
   244  				extractedOrders = append(extractedOrders, order.Clone())
   245  				totalVolume += order.TrueRemaining()
   246  				// Remove the order from the price level
   247  				toRemove++
   248  			} else {
   249  				// We should never get to here unless the passed in price
   250  				// and volume are not correct
   251  				s.log.Panic("Failed to extract orders as not enough volume within price limits",
   252  					logging.BigUint("price", price),
   253  					logging.Uint64("required-volume", volume),
   254  					logging.Uint64("found-volume", totalVolume),
   255  					logging.Bool("remove-orders", removeOrders))
   256  			}
   257  
   258  			// If we have the right amount, stop processing
   259  			if totalVolume == volume {
   260  				break
   261  			}
   262  		}
   263  
   264  		if removeOrders {
   265  			for ; toRemove > 0; toRemove-- {
   266  				pricelevel.removeOrder(0)
   267  			}
   268  			// Erase this price level which will be at the end of the slice
   269  			if len(pricelevel.orders) == 0 {
   270  				s.levels[i] = nil
   271  				s.levels = s.levels[:len(s.levels)-1]
   272  			}
   273  		}
   274  
   275  		// Check if we have done enough
   276  		if totalVolume == volume {
   277  			break
   278  		}
   279  	}
   280  	// If we get here and don't have the full amount of volume
   281  	// something has gone wrong
   282  	if totalVolume != volume {
   283  		s.log.Panic("Failed to extract orders as not enough volume on the book",
   284  			logging.BigUint("price", price),
   285  			logging.Uint64("volume", volume),
   286  			logging.Uint64("total-volume", totalVolume),
   287  		)
   288  	}
   289  
   290  	return extractedOrders
   291  }
   292  
   293  // RemoveOrder will remove an order from the book.
   294  func (s *OrderBookSide) RemoveOrder(o *types.Order) (*types.Order, error) {
   295  	// first  we try to find the pricelevel of the order
   296  	var i int
   297  	if o.Side == types.SideBuy {
   298  		i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.GTE(o.Price) })
   299  	} else {
   300  		// sell side levels should be ordered in ascending
   301  		i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.LTE(o.Price) })
   302  	}
   303  	// we did not found the level
   304  	// then the order do not exists in the price level
   305  	if i >= len(s.levels) {
   306  		return nil, types.ErrOrderNotFound
   307  	}
   308  
   309  	// now we may have a few orders with the same timestamp
   310  	// lets iterate over them in order to find the right one
   311  	finaloidx := -1
   312  	for index, order := range s.levels[i].orders {
   313  		if order.ID == o.ID {
   314  			finaloidx = index
   315  			break
   316  		}
   317  	}
   318  
   319  	var order *types.Order
   320  	// remove the order from the
   321  	if finaloidx != -1 {
   322  		order = s.levels[i].orders[finaloidx]
   323  		s.levels[i].removeOrder(finaloidx)
   324  	} else {
   325  		// We could not find the matching order, return an error
   326  		return nil, types.ErrOrderNotFound
   327  	}
   328  
   329  	if len(s.levels[i].orders) <= 0 {
   330  		s.levels = s.levels[:i+copy(s.levels[i:], s.levels[i+1:])]
   331  	}
   332  
   333  	return order, nil
   334  }
   335  
   336  func (s *OrderBookSide) getPriceLevelIfExists(price *num.Uint) *PriceLevel {
   337  	var i int
   338  	if s.side == types.SideBuy {
   339  		// buy side levels should be ordered in ascending
   340  		i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.GTE(price) })
   341  	} else {
   342  		// sell side levels should be ordered in descending
   343  		i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.LTE(price) })
   344  	}
   345  
   346  	// we found the level just return it.
   347  	if i < len(s.levels) && s.levels[i].price.EQ(price) {
   348  		return s.levels[i]
   349  	}
   350  	return nil
   351  }
   352  
   353  func (s *OrderBookSide) getPriceLevel(price *num.Uint) *PriceLevel {
   354  	var i int
   355  	if s.side == types.SideBuy {
   356  		// buy side levels should be ordered in ascending
   357  		i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.GTE(price) })
   358  	} else {
   359  		// sell side levels should be ordered in descending
   360  		i = sort.Search(len(s.levels), func(i int) bool { return s.levels[i].price.LTE(price) })
   361  	}
   362  
   363  	// we found the level just return it.
   364  	if i < len(s.levels) && s.levels[i].price.EQ(price) {
   365  		return s.levels[i]
   366  	}
   367  
   368  	// append new elem first to make sure we have enough place
   369  	// this would reallocate sufficiently then
   370  	// no risk of this being a empty order, as it's overwritten just next with
   371  	// the slice insert
   372  	level := NewPriceLevel(price.Clone())
   373  	s.levels = append(s.levels, nil)
   374  	copy(s.levels[i+1:], s.levels[i:])
   375  	s.levels[i] = level
   376  	return level
   377  }
   378  
   379  func (s *OrderBookSide) getLevelsForPrice(price *num.Uint) []*PriceLevel {
   380  	ret := make([]*PriceLevel, 0, len(s.levels))
   381  	// buy is ASCENDING, start at the highest buy price until we find a buy order that will not trade
   382  	// at the given price (ie given price is > buy order price).
   383  	cmpF := price.GT
   384  	if s.side == types.SideSell {
   385  		// sell is DESCENDING, start at the lowest sell order price, until we find a sell order that won't trade
   386  		// at the given price (ie given price < sell order price).
   387  		cmpF = price.LT
   388  	}
   389  	for i := len(s.levels) - 1; i >= 0; i-- {
   390  		if cmpF(s.levels[i].price) {
   391  			return ret
   392  		}
   393  		ret = append(ret, s.levels[i])
   394  	}
   395  	return ret
   396  }
   397  
   398  // GetVolume returns the volume at the given pricelevel.
   399  func (s *OrderBookSide) GetVolume(price *num.Uint) (uint64, error) {
   400  	priceLevel := s.getPriceLevelIfExists(price)
   401  
   402  	if priceLevel == nil {
   403  		return 0, ErrPriceNotFound
   404  	}
   405  
   406  	return priceLevel.volume, nil
   407  }
   408  
   409  // fakeUncross returns hypothetical trades if the order book side were to be uncrossed with the agg order supplied,
   410  // checkWashTrades checks non-FOK orders for wash trades if set to true (FOK orders are always checked for wash trades).
   411  func (s *OrderBookSide) fakeUncross(agg *types.Order, checkWashTrades bool) ([]*types.Trade, error) {
   412  	defer s.uncrossFinished()
   413  
   414  	// get a copy of the order passed in, so we can rely on fakeUncross to do its job
   415  	fake := agg.Clone()
   416  
   417  	var (
   418  		trades            []*types.Trade
   419  		offbookOrders     []*types.Order
   420  		totalVolumeToFill uint64
   421  	)
   422  	if agg.TimeInForce == types.OrderTimeInForceFOK {
   423  		var checkPrice func(*num.Uint) bool
   424  		if agg.Side == types.SideBuy {
   425  			checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.LTE(agg.Price) }
   426  		} else {
   427  			checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.GTE(agg.Price) }
   428  		}
   429  
   430  		// first check for volume between the theoretical best price and the first price level
   431  		_, oo := s.uncrossOffbook(len(s.levels), fake, true)
   432  		for _, order := range oo {
   433  			totalVolumeToFill += order.Remaining
   434  		}
   435  
   436  		for i := len(s.levels) - 1; i >= 0; i-- {
   437  			level := s.levels[i]
   438  			// we don't have to account for network orders, they don't apply in price monitoring
   439  			// nor do fees apply
   440  			if checkPrice(level.price) || agg.Type == types.OrderTypeMarket {
   441  				for _, order := range level.orders {
   442  					if agg.Party == order.Party {
   443  						return nil, ErrWashTrade
   444  					}
   445  					totalVolumeToFill += order.Remaining
   446  					if totalVolumeToFill >= agg.Remaining {
   447  						break
   448  					}
   449  				}
   450  
   451  				_, oo := s.uncrossOffbook(i, fake, true)
   452  				for _, order := range oo {
   453  					totalVolumeToFill += order.Remaining
   454  				}
   455  			}
   456  
   457  			if totalVolumeToFill >= agg.Remaining {
   458  				break
   459  			}
   460  		}
   461  
   462  		// FOK order could not be filled
   463  		if totalVolumeToFill < agg.Remaining {
   464  			return nil, nil
   465  		}
   466  
   467  		// reset the offbook source so we can then do it all again....
   468  		s.uncrossFinished()
   469  	}
   470  
   471  	// get a copy of the order passed in, so we can rely on fakeUncross to do its job
   472  	fake = agg.Clone()
   473  	var (
   474  		idx        = len(s.levels) - 1
   475  		ntrades    []*types.Trade
   476  		err        error
   477  		checkPrice func(*num.Uint) bool
   478  	)
   479  
   480  	if fake.Side == types.SideBuy {
   481  		checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.GT(agg.Price) }
   482  	} else {
   483  		checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.LT(agg.Price) }
   484  	}
   485  
   486  	trades, offbookOrders = s.uncrossOffbook(idx+1, fake, true)
   487  
   488  	// in here we iterate from the end, as it's easier to remove the
   489  	// price levels from the back of the slice instead of from the front
   490  	// also it will allow us to reduce allocations
   491  	for idx >= 0 && fake.Remaining > 0 {
   492  		// not a market order && buy side price is too low => break
   493  		if agg.Type != types.OrderTypeMarket && checkPrice(s.levels[idx].price) {
   494  			break
   495  		}
   496  		fake, ntrades, err = s.levels[idx].fakeUncross(fake, checkWashTrades)
   497  		trades = append(trades, ntrades...)
   498  		// break if a wash trade is detected
   499  		if err != nil && err == ErrWashTrade {
   500  			break
   501  		}
   502  
   503  		if fake.Remaining != 0 {
   504  			obTrades, obOrders := s.uncrossOffbook(idx, fake, true)
   505  			trades = append(trades, obTrades...)
   506  			offbookOrders = append(offbookOrders, obOrders...)
   507  		}
   508  
   509  		// the orders are still part of the levels, so we just have to move on anyway
   510  		idx--
   511  	}
   512  
   513  	return trades, err
   514  }
   515  
   516  // fakeUncrossAuction returns hypothetical trades if the order book side were to be uncrossed with the agg orders supplied, wash trades are allowed.
   517  func (s *OrderBookSide) fakeUncrossAuction(orders []*types.Order) ([]*types.Trade, error) {
   518  	defer s.uncrossFinished()
   519  	// in here we iterate from the end, as it's easier to remove the
   520  	// price levels from the back of the slice instead of from the front
   521  	// also it will allow us to reduce allocations
   522  	nOrders := len(orders)
   523  	if nOrders == 0 {
   524  		return []*types.Trade{}, nil
   525  	}
   526  
   527  	checkPrice := func(levelPrice *num.Uint, order *types.Order) bool {
   528  		if order.Side == types.SideBuy {
   529  			return levelPrice.GT(order.Price)
   530  		}
   531  		return levelPrice.LT(order.Price)
   532  	}
   533  
   534  	var (
   535  		ntrades []*types.Trade
   536  		iOrder  = 0
   537  		trades  []*types.Trade
   538  		lvl     *PriceLevel
   539  		err     error
   540  		fake    *types.Order
   541  	)
   542  
   543  	for ; iOrder < len(orders); iOrder++ {
   544  		fake = orders[iOrder].Clone()
   545  		ntrades, _ = s.uncrossOffbook(len(s.levels), fake, false)
   546  		trades = append(trades, ntrades...)
   547  
   548  		// no more to trade in this pre-orderbook region for AMM's, we now need to move to orderbook
   549  		if fake.Remaining != 0 {
   550  			break
   551  		}
   552  	}
   553  
   554  	if iOrder >= nOrders {
   555  		return trades, nil
   556  	}
   557  
   558  	for idx := len(s.levels) - 1; idx >= 0; idx-- {
   559  		// since all of uncrossOrders will be traded away and at the same uncrossing price
   560  		// iceberg orders are sent in as their full value instead of refreshing at each step
   561  		if fake.IcebergOrder != nil {
   562  			fake.Remaining += fake.IcebergOrder.ReservedRemaining
   563  			fake.IcebergOrder.ReservedRemaining = 0
   564  		}
   565  
   566  		haveOffbookVolume := true
   567  
   568  		// clone price level
   569  		lvl = clonePriceLevel(s.levels[idx])
   570  		for lvl.volume > 0 || haveOffbookVolume {
   571  			// not a market order && buy side price is too low => continue
   572  			if fake.Type != types.OrderTypeMarket && checkPrice(lvl.price, fake) {
   573  				break
   574  			}
   575  
   576  			_, ntrades, _, err = lvl.uncross(fake, false)
   577  			if err != nil {
   578  				return nil, err
   579  			}
   580  			trades = append(trades, ntrades...)
   581  
   582  			if fake.Remaining != 0 {
   583  				ntrades, _ := s.uncrossOffbook(idx, fake, true)
   584  				trades = append(trades, ntrades...)
   585  
   586  				// if we couldn't consume the whole order with this AMM volume in this region
   587  				// we need to move onto the next orderbook price level
   588  				if fake.Remaining != 0 {
   589  					haveOffbookVolume = false
   590  				}
   591  			}
   592  
   593  			if fake.Remaining == 0 {
   594  				iOrder++
   595  				if iOrder >= nOrders {
   596  					return trades, nil
   597  				}
   598  				fake = orders[iOrder].Clone()
   599  			}
   600  		}
   601  	}
   602  	return trades, nil
   603  }
   604  
   605  func clonePriceLevel(lvl *PriceLevel) *PriceLevel {
   606  	orders := make([]*types.Order, 0, len(lvl.orders))
   607  	for _, o := range lvl.orders {
   608  		orders = append(orders, o.Clone())
   609  	}
   610  	return &PriceLevel{
   611  		price:  lvl.price.Clone(),
   612  		orders: orders,
   613  		volume: lvl.volume,
   614  	}
   615  }
   616  
   617  // betweenLevels returns the inner, outer bounds for the given idx in the price levels.
   618  // Usually this means (inner, outer) = (lvl[i].price, lvl[i-1].price) but we also handle
   619  // the past the first and last price levels.
   620  func (s *OrderBookSide) betweenLevels(idx int, last *num.Uint) (*num.Uint, *num.Uint) {
   621  	// there are no price levels, so between is from low to high
   622  	if len(s.levels) == 0 {
   623  		return nil, last
   624  	}
   625  
   626  	// we're at the first price level, we pass back nil for the first level since we do not know the bound for this
   627  	if idx == len(s.levels) {
   628  		return nil, s.levels[idx-1].price
   629  	}
   630  
   631  	// we're at the last price level
   632  	if idx == 0 {
   633  		return s.levels[0].price, last
   634  	}
   635  	return s.levels[idx].price, s.levels[idx-1].price
   636  }
   637  
   638  func (s *OrderBookSide) uncrossFinished() {
   639  	if s.offbook != nil {
   640  		s.offbook.NotifyFinished()
   641  	}
   642  }
   643  
   644  func (s *OrderBookSide) uncrossOffbook(idx int, agg *types.Order, fake bool) ([]*types.Trade, []*types.Order) {
   645  	if s.offbook == nil {
   646  		return nil, nil
   647  	}
   648  
   649  	// get the bounds between price levels for the given price level index
   650  	inner, outer := s.betweenLevels(idx, agg.Price)
   651  
   652  	// submit the order to the offbook source for volume between those bounds
   653  	orders := s.offbook.SubmitOrder(agg, inner, outer)
   654  
   655  	trades := make([]*types.Trade, 0, len(orders))
   656  	for _, o := range orders {
   657  		size := min(agg.Remaining, o.Remaining)
   658  		trade := newTrade(agg, o, size)
   659  		agg.Remaining -= size
   660  		if !fake {
   661  			o.Remaining -= size
   662  		}
   663  		trades = append(trades, trade)
   664  	}
   665  
   666  	return trades, orders
   667  }
   668  
   669  // uncross returns trades after order book side gets uncrossed with the agg order supplied,
   670  // checkWashTrades checks non-FOK orders for wash trades if set to true (FOK orders are always checked for wash trades).
   671  func (s *OrderBookSide) uncross(agg *types.Order, checkWashTrades bool) ([]*types.Trade, []*types.Order, *num.Uint, error) {
   672  	var (
   673  		trades            []*types.Trade
   674  		impactedOrders    []*types.Order
   675  		lastTradedPrice   = num.UintZero()
   676  		totalVolumeToFill uint64
   677  		checkPrice        func(*num.Uint) bool
   678  	)
   679  
   680  	fake := agg.Clone()
   681  
   682  	if agg.Side == types.SideSell {
   683  		checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.GTE(agg.Price) }
   684  	} else {
   685  		checkPrice = func(levelPrice *num.Uint) bool { return levelPrice.LTE(agg.Price) }
   686  	}
   687  
   688  	if agg.TimeInForce == types.OrderTimeInForceFOK {
   689  		_, oo := s.uncrossOffbook(len(s.levels), fake, true)
   690  		for _, order := range oo {
   691  			totalVolumeToFill += order.Remaining
   692  		}
   693  
   694  		// Process these backwards
   695  		for i := len(s.levels) - 1; i >= 0; i-- {
   696  			level := s.levels[i]
   697  			if checkPrice(level.price) || agg.Type == types.OrderTypeMarket || agg.Type == types.OrderTypeNetwork {
   698  				// We have to process every order to check for wash trades
   699  				for _, order := range level.orders {
   700  					// Check for wash trading
   701  					if agg.Party == order.Party {
   702  						// Stop the order and return
   703  						agg.Status = types.OrderStatusStopped
   704  						return nil, nil, lastTradedPrice, ErrWashTrade
   705  					}
   706  					// in case of network trades, we want to calculate an accurate average price to return
   707  					totalVolumeToFill += order.Remaining
   708  
   709  					_, oo := s.uncrossOffbook(i, fake, true)
   710  					for _, order := range oo {
   711  						totalVolumeToFill += order.Remaining
   712  					}
   713  
   714  					if totalVolumeToFill >= agg.Remaining {
   715  						break
   716  					}
   717  				}
   718  			}
   719  			if totalVolumeToFill >= agg.Remaining {
   720  				break
   721  			}
   722  		}
   723  
   724  		if s.log.GetLevel() == logging.DebugLevel {
   725  			s.log.Debug(fmt.Sprintf("totalVolumeToFill %d until price %d, remaining %d\n", totalVolumeToFill, agg.Price, agg.Remaining))
   726  		}
   727  
   728  		if totalVolumeToFill < agg.Remaining {
   729  			return trades, impactedOrders, lastTradedPrice, nil
   730  		}
   731  
   732  		// reset the offsource book so we can then do it all again....
   733  		s.uncrossFinished()
   734  	}
   735  
   736  	var (
   737  		idx     = len(s.levels) - 1
   738  		filled  bool
   739  		ntrades []*types.Trade
   740  		nimpact []*types.Order
   741  		err     error
   742  	)
   743  
   744  	// first check for off source volume between the best theoretical price and the first price level
   745  	trades, impactedOrders = s.uncrossOffbook(idx+1, agg, false)
   746  	filled = agg.Remaining == 0
   747  
   748  	// in here we iterate from the end, as it's easier to remove the
   749  	// price levels from the back of the slice instead of from the front
   750  	// also it will allow us to reduce allocations
   751  	for !filled && idx >= 0 {
   752  		if checkPrice(s.levels[idx].price) || agg.Type == types.OrderTypeMarket || agg.Type == types.OrderTypeNetwork {
   753  			filled, ntrades, nimpact, err = s.levels[idx].uncross(agg, checkWashTrades)
   754  			trades = append(trades, ntrades...)
   755  			impactedOrders = append(impactedOrders, nimpact...)
   756  			// break if a wash trade is detected
   757  			if err != nil && err == ErrWashTrade {
   758  				break
   759  			}
   760  
   761  			if !filled {
   762  				// now check for off source volume between the price levels
   763  				ot, oo := s.uncrossOffbook(idx, agg, false)
   764  				trades = append(trades, ot...)
   765  				impactedOrders = append(impactedOrders, oo...)
   766  				filled = agg.Remaining == 0
   767  			}
   768  
   769  			if len(s.levels[idx].orders) <= 0 {
   770  				idx--
   771  			}
   772  		} else {
   773  			break
   774  		}
   775  	}
   776  
   777  	// now we nil the price levels that have been completely emptied out
   778  	// then we resize the slice
   779  	if idx < 0 || len(s.levels[idx].orders) > 0 {
   780  		// do not remove this one as it's not emptied already
   781  		idx++
   782  	}
   783  	if idx < len(s.levels) {
   784  		// nil out the pricelevels so they get collected at some point
   785  		for i := idx; i < len(s.levels); i++ {
   786  			s.levels[i] = nil
   787  		}
   788  		s.levels = s.levels[:idx]
   789  	}
   790  
   791  	if agg.Type == types.OrderTypeNetwork {
   792  		totalPrice := num.UintZero()
   793  		for _, t := range trades {
   794  			// totalPrice += t.Price * t.Size
   795  			totalPrice.Add(
   796  				totalPrice,
   797  				num.UintZero().Mul(t.Price, num.NewUint(t.Size)),
   798  			)
   799  		}
   800  		// now we are done with uncrossing,
   801  		// we can set back the price of the netorder to the average
   802  		// price over the whole volume
   803  		// agg.Price = totalPrice / agg.Size
   804  		agg.Price.Div(totalPrice, num.NewUint(agg.Size))
   805  	}
   806  
   807  	if len(trades) > 0 {
   808  		lastTradedPrice = trades[len(trades)-1].Price.Clone()
   809  	}
   810  	return trades, impactedOrders, lastTradedPrice, err
   811  }
   812  
   813  func (s *OrderBookSide) getLevels() []*PriceLevel {
   814  	return s.levels
   815  }
   816  
   817  func (s *OrderBookSide) getOrderCount() int64 {
   818  	var orderCount int64
   819  	for _, level := range s.levels {
   820  		orderCount = orderCount + int64(len(level.orders))
   821  	}
   822  	return orderCount
   823  }
   824  
   825  func (s *OrderBookSide) getTotalVolume() int64 {
   826  	var volume int64
   827  	for _, level := range s.levels {
   828  		volume = volume + int64(level.volume)
   829  	}
   830  	return volume
   831  }