decred.org/dcrdex@v1.0.5/server/book/book.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  // Package book defines the order book used by each Market.
     5  package book
     6  
     7  import (
     8  	"sync"
     9  
    10  	"decred.org/dcrdex/dex/order"
    11  	"decred.org/dcrdex/server/account"
    12  )
    13  
    14  const (
    15  	// initBookHalfCapacity is the default capacity of one side (buy or sell) of
    16  	// the order book. It is set to 2^16 orders (65536 orders) per book side.
    17  	initBookHalfCapacity uint32 = 1 << 16
    18  )
    19  
    20  // Book is a market's order book. The Book uses a configurable lot size, of
    21  // which all inserted orders must have a quantity that is a multiple. The buy
    22  // and sell sides of the order book are contained in separate priority queues to
    23  // allow constant time access to the best orders, and log time insertion and
    24  // removal of orders.
    25  type Book struct {
    26  	mtx          sync.RWMutex
    27  	lotSize      uint64
    28  	buys         *OrderPQ
    29  	sells        *OrderPQ
    30  	acctTracking AccountTracking
    31  	acctTracker  *accountTracker
    32  }
    33  
    34  // New creates a new order book with the given lot size and account-tracking.
    35  func New(lotSize uint64, acctTracking AccountTracking) *Book {
    36  	return &Book{
    37  		lotSize:      lotSize,
    38  		buys:         NewMaxOrderPQ(initBookHalfCapacity),
    39  		sells:        NewMinOrderPQ(initBookHalfCapacity),
    40  		acctTracking: acctTracking,
    41  		acctTracker:  newAccountTracker(acctTracking),
    42  	}
    43  }
    44  
    45  // Clear reset the order book with configured capacity.
    46  func (b *Book) Clear() (removedBuys, removedSells []*order.LimitOrder) {
    47  	b.mtx.Lock()
    48  	removedBuys, removedSells = b.buys.Orders(), b.sells.Orders()
    49  	b.buys, b.sells = nil, nil
    50  	b.buys = NewMaxOrderPQ(initBookHalfCapacity)
    51  	b.sells = NewMinOrderPQ(initBookHalfCapacity)
    52  	b.acctTracker = newAccountTracker(b.acctTracking)
    53  	b.mtx.Unlock()
    54  	return
    55  }
    56  
    57  // LotSize returns the Book's configured lot size in atoms of the base asset.
    58  func (b *Book) LotSize() uint64 {
    59  	return b.lotSize
    60  }
    61  
    62  // BuyCount returns the number of buy orders.
    63  func (b *Book) BuyCount() int {
    64  	return b.buys.Count()
    65  }
    66  
    67  // SellCount returns the number of sell orders.
    68  func (b *Book) SellCount() int {
    69  	return b.sells.Count()
    70  }
    71  
    72  // BestSell returns a pointer to the best sell order in the order book. The
    73  // order is NOT removed from the book.
    74  func (b *Book) BestSell() *order.LimitOrder {
    75  	return b.sells.PeekBest()
    76  }
    77  
    78  // BestBuy returns a pointer to the best buy order in the order book. The
    79  // order is NOT removed from the book.
    80  func (b *Book) BestBuy() *order.LimitOrder {
    81  	return b.buys.PeekBest()
    82  }
    83  
    84  // Best returns pointers to the best buy and sell order in the order book. The
    85  // orders are NOT removed from the book.
    86  func (b *Book) Best() (bestBuy, bestSell *order.LimitOrder) {
    87  	b.mtx.RLock()
    88  	bestBuy = b.buys.PeekBest()
    89  	bestSell = b.sells.PeekBest()
    90  	b.mtx.RUnlock()
    91  	return
    92  }
    93  
    94  // Insert attempts to insert the provided order into the order book, returning a
    95  // boolean indicating if the insertion was successful. If the order is not an
    96  // integer multiple of the Book's lot size, the order will not be inserted.
    97  func (b *Book) Insert(o *order.LimitOrder) bool {
    98  	if o.Quantity%b.lotSize != 0 {
    99  		log.Warnf("(*Book).Insert: Refusing to insert an order with a quantity that is not a multiple of lot size.")
   100  		return false
   101  	}
   102  	b.mtx.Lock()
   103  	defer b.mtx.Unlock()
   104  	if o.Sell {
   105  		if b.sells.Insert(o) {
   106  			b.acctTracker.add(o)
   107  			return true
   108  		}
   109  		return false
   110  	}
   111  	if b.buys.Insert(o) {
   112  		b.acctTracker.add(o)
   113  		return true
   114  	}
   115  	return false
   116  }
   117  
   118  // Remove attempts to remove the order with the given OrderID from the book.
   119  func (b *Book) Remove(oid order.OrderID) (*order.LimitOrder, bool) {
   120  	b.mtx.Lock()
   121  	defer b.mtx.Unlock()
   122  	if removed, ok := b.sells.RemoveOrderID(oid); ok {
   123  		b.acctTracker.remove(removed)
   124  		return removed, true
   125  	}
   126  	if removed, ok := b.buys.RemoveOrderID(oid); ok {
   127  		b.acctTracker.remove(removed)
   128  		return removed, true
   129  	}
   130  	return nil, false
   131  }
   132  
   133  // RemoveUserOrders removes all orders from the book that belong to a user. The
   134  // removed buy and sell orders are returned.
   135  func (b *Book) RemoveUserOrders(user account.AccountID) (removedBuys, removedSells []*order.LimitOrder) {
   136  	removedBuys = b.buys.RemoveUserOrders(user)
   137  	for _, lo := range removedBuys {
   138  		b.acctTracker.remove(lo)
   139  	}
   140  	removedSells = b.sells.RemoveUserOrders(user)
   141  	for _, lo := range removedSells {
   142  		b.acctTracker.remove(lo)
   143  	}
   144  	return
   145  }
   146  
   147  // HaveOrder checks if an order is in either the buy or sell side of the book.
   148  func (b *Book) HaveOrder(oid order.OrderID) bool {
   149  	b.mtx.RLock()
   150  	defer b.mtx.RUnlock()
   151  	return b.buys.HaveOrder(oid) || b.sells.HaveOrder(oid)
   152  }
   153  
   154  // Order attempts to retrieve an order from either the buy or sell side of the
   155  // book. If the order data is not required, consider using HaveOrder.
   156  func (b *Book) Order(oid order.OrderID) *order.LimitOrder {
   157  	b.mtx.RLock()
   158  	defer b.mtx.RUnlock()
   159  	if lo := b.buys.Order(oid); lo != nil {
   160  		return lo
   161  	}
   162  	return b.sells.Order(oid)
   163  }
   164  
   165  // UserOrderTotals returns the total amount in booked orders and the number of
   166  // booked orders, for both the buy and sell sides of the book. Both amounts are
   167  // in units of the base asset, and should be multiples of the market's lot size.
   168  func (b *Book) UserOrderTotals(user account.AccountID) (buyAmt, sellAmt, buyCount, sellCount uint64) {
   169  	b.mtx.RLock()
   170  	buyAmt, buyCount = b.buys.UserOrderTotals(user)
   171  	sellAmt, sellCount = b.sells.UserOrderTotals(user)
   172  	b.mtx.RUnlock()
   173  	return
   174  }
   175  
   176  // SellOrders copies out all sell orders in the book, sorted.
   177  func (b *Book) SellOrders() []*order.LimitOrder {
   178  	return b.sells.Orders()
   179  }
   180  
   181  // SellOrdersN copies out the N best sell orders in the book, sorted.
   182  func (b *Book) SellOrdersN(N int) []*order.LimitOrder {
   183  	return b.sells.OrdersN(N)
   184  }
   185  
   186  // BuyOrders copies out all buy orders in the book, sorted.
   187  func (b *Book) BuyOrders() []*order.LimitOrder {
   188  	return b.buys.Orders()
   189  }
   190  
   191  // BuyOrdersN copies out the N best buy orders in the book, sorted.
   192  func (b *Book) BuyOrdersN(N int) []*order.LimitOrder {
   193  	return b.buys.OrdersN(N)
   194  }
   195  
   196  // UnfilledUserBuys retrieves all buy orders belonging to a given user that are
   197  // completely unfilled.
   198  func (b *Book) UnfilledUserBuys(user account.AccountID) []*order.LimitOrder {
   199  	b.mtx.RLock()
   200  	defer b.mtx.RUnlock()
   201  	return b.buys.UnfilledForUser(user)
   202  }
   203  
   204  // UnfilledUserSells retrieves all sell orders belonging to a given user that
   205  // are completely unfilled.
   206  func (b *Book) UnfilledUserSells(user account.AccountID) []*order.LimitOrder {
   207  	b.mtx.RLock()
   208  	defer b.mtx.RUnlock()
   209  	return b.sells.UnfilledForUser(user)
   210  }
   211  
   212  // IterateBaseAccount calls the provided function for every tracked order with
   213  // a base asset corresponding to the specified account address.
   214  func (b *Book) IterateBaseAccount(acctAddr string, f func(lo *order.LimitOrder)) {
   215  	b.mtx.RLock()
   216  	defer b.mtx.RUnlock()
   217  	b.acctTracker.iterateBaseAccount(acctAddr, f)
   218  }
   219  
   220  // IterateQuoteAccount calls the provided function for every tracked order with
   221  // a quote asset corresponding to the specified account address.
   222  func (b *Book) IterateQuoteAccount(acctAddr string, f func(lo *order.LimitOrder)) {
   223  	b.mtx.RLock()
   224  	defer b.mtx.RUnlock()
   225  	b.acctTracker.iterateQuoteAccount(acctAddr, f)
   226  }