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