decred.org/dcrdex@v1.0.3/client/mm/libxc/orderbook.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  package libxc
     4  
     5  import (
     6  	"fmt"
     7  	"math"
     8  	"sync"
     9  
    10  	"github.com/huandu/skiplist"
    11  )
    12  
    13  type obEntry struct {
    14  	qty  uint64
    15  	rate uint64
    16  }
    17  
    18  // obEntryComparable is a skiplist.Comparable implementation for
    19  // obEntry. It is used to sort the orderbook entries by rate.
    20  type obEntryComparable int
    21  
    22  const bidsComparable = obEntryComparable(0)
    23  const asksComparable = obEntryComparable(1)
    24  
    25  var _ skiplist.Comparable = (*obEntryComparable)(nil)
    26  
    27  func (o obEntryComparable) Compare(lhs, rhs interface{}) int {
    28  	lhsEntry := lhs.(*obEntry)
    29  	rhsEntry := rhs.(*obEntry)
    30  
    31  	var toReturn int
    32  	if lhsEntry.rate < rhsEntry.rate {
    33  		toReturn = -1
    34  	}
    35  	if lhsEntry.rate > rhsEntry.rate {
    36  		toReturn = 1
    37  	}
    38  
    39  	if o == bidsComparable {
    40  		toReturn = -toReturn
    41  	}
    42  
    43  	return toReturn
    44  }
    45  
    46  func (o obEntryComparable) CalcScore(key interface{}) float64 {
    47  	if o == bidsComparable {
    48  		return math.MaxFloat64 - float64(key.(*obEntry).rate)
    49  	} else {
    50  		return float64(key.(*obEntry).rate)
    51  	}
    52  }
    53  
    54  // orderbook is an implementation of a limit order book that allows for quick
    55  // updates and calculation of the volume weighted average price (VWAP).
    56  type orderbook struct {
    57  	mtx  sync.RWMutex
    58  	bids skiplist.SkipList
    59  	asks skiplist.SkipList
    60  }
    61  
    62  func newOrderBook() *orderbook {
    63  	return &orderbook{
    64  		bids: *skiplist.New(bidsComparable),
    65  		asks: *skiplist.New(asksComparable),
    66  	}
    67  }
    68  
    69  func (ob *orderbook) String() string {
    70  	ob.mtx.RLock()
    71  	defer ob.mtx.RUnlock()
    72  
    73  	bids := make([]obEntry, 0, ob.bids.Len())
    74  	for curr := ob.bids.Front(); curr != nil; curr = curr.Next() {
    75  		bids = append(bids, *curr.Value.(*obEntry))
    76  	}
    77  
    78  	asks := make([]obEntry, 0, ob.asks.Len())
    79  	for curr := ob.asks.Front(); curr != nil; curr = curr.Next() {
    80  		asks = append(asks, *curr.Value.(*obEntry))
    81  	}
    82  
    83  	return fmt.Sprintf("bids: %v, asks: %v", bids, asks)
    84  }
    85  
    86  // update updates the orderbook new quantities at the given rates.
    87  // If the quantity is 0, the entry is removed from the orderbook.
    88  func (ob *orderbook) update(bids []*obEntry, asks []*obEntry) {
    89  	ob.mtx.Lock()
    90  	defer ob.mtx.Unlock()
    91  
    92  	for _, entry := range bids {
    93  		if entry.qty == 0 {
    94  			ob.bids.Remove(entry)
    95  			continue
    96  		}
    97  		ob.bids.Set(entry, entry)
    98  	}
    99  
   100  	for _, entry := range asks {
   101  		if entry.qty == 0 {
   102  			ob.asks.Remove(entry)
   103  			continue
   104  		}
   105  		ob.asks.Set(entry, entry)
   106  	}
   107  }
   108  
   109  func (ob *orderbook) clear() {
   110  	ob.mtx.Lock()
   111  	defer ob.mtx.Unlock()
   112  
   113  	ob.bids = *skiplist.New(bidsComparable)
   114  	ob.asks = *skiplist.New(asksComparable)
   115  }
   116  
   117  func (ob *orderbook) vwap(bids bool, qty uint64) (vwap, extrema uint64, filled bool) {
   118  	if qty == 0 { // avoid division by zero
   119  		return 0, 0, false
   120  	}
   121  
   122  	ob.mtx.RLock()
   123  	defer ob.mtx.RUnlock()
   124  
   125  	var list *skiplist.SkipList
   126  	if bids {
   127  		list = &ob.bids
   128  	} else {
   129  		list = &ob.asks
   130  	}
   131  
   132  	remaining := qty
   133  	var weightedSum uint64
   134  	for curr := list.Front(); curr != nil; curr = curr.Next() {
   135  		if curr == nil {
   136  			break
   137  		}
   138  		entry := curr.Value.(*obEntry)
   139  		extrema = entry.rate
   140  		if entry.qty >= remaining {
   141  			filled = true
   142  			weightedSum += remaining * extrema
   143  			break
   144  		}
   145  		remaining -= entry.qty
   146  		weightedSum += entry.qty * extrema
   147  	}
   148  	if !filled {
   149  		return 0, 0, false
   150  	}
   151  
   152  	return weightedSum / qty, extrema, filled
   153  }
   154  
   155  func (ob *orderbook) midGap() uint64 {
   156  	ob.mtx.RLock()
   157  	defer ob.mtx.RUnlock()
   158  
   159  	bestBuyI := ob.bids.Front()
   160  	if bestBuyI == nil {
   161  		return 0
   162  	}
   163  	bestSellI := ob.asks.Front()
   164  	if bestSellI == nil {
   165  		return 0
   166  	}
   167  	bestBuy, bestSell := bestBuyI.Value.(*obEntry), bestSellI.Value.(*obEntry)
   168  	return (bestBuy.rate + bestSell.rate) / 2
   169  }
   170  
   171  // snap generates a snapshot of the book.
   172  func (ob *orderbook) snap() (bids, asks []*obEntry) {
   173  	ob.mtx.RLock()
   174  	defer ob.mtx.RUnlock()
   175  
   176  	bids = make([]*obEntry, 0, ob.bids.Len())
   177  	for curr := ob.bids.Front(); curr != nil; curr = curr.Next() {
   178  		bids = append(bids, curr.Value.(*obEntry))
   179  	}
   180  
   181  	asks = make([]*obEntry, 0, ob.asks.Len())
   182  	for curr := ob.asks.Front(); curr != nil; curr = curr.Next() {
   183  		asks = append(asks, curr.Value.(*obEntry))
   184  	}
   185  
   186  	return bids, asks
   187  }