decred.org/dcrdex@v1.0.5/client/mm/libxc/orderbook_test.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 libxc
     5  
     6  import (
     7  	"reflect"
     8  	"sort"
     9  	"testing"
    10  )
    11  
    12  func TestOrderbook(t *testing.T) {
    13  	ob := newOrderBook()
    14  
    15  	// Test vwap on empty books
    16  	_, _, filled := ob.vwap(true, 1)
    17  	if filled {
    18  		t.Fatalf("empty book should not be filled")
    19  	}
    20  	_, _, filled = ob.vwap(false, 1)
    21  	if filled {
    22  		t.Fatalf("empty book should not be filled")
    23  	}
    24  
    25  	sortedEntries := func(bids bool, entries []*obEntry) []*obEntry {
    26  		sorted := make([]*obEntry, len(entries))
    27  		copy(sorted, entries)
    28  		sort.Slice(sorted, func(i, j int) bool {
    29  			if bids {
    30  				return sorted[i].rate > sorted[j].rate
    31  			}
    32  			return sorted[i].rate < sorted[j].rate
    33  		})
    34  		return sorted
    35  	}
    36  
    37  	// Populate the book with some bids and asks. They both
    38  	// have the same values, but VWAP for asks should be
    39  	// calculated from the lower values first.
    40  	bids := []*obEntry{
    41  		{qty: 30e8, rate: 4000},
    42  		{qty: 30e8, rate: 5000},
    43  		{qty: 80e8, rate: 400},
    44  		{qty: 10e8, rate: 3000},
    45  	}
    46  	asks := []*obEntry{
    47  		{qty: 30e8, rate: 4000},
    48  		{qty: 30e8, rate: 5000},
    49  		{qty: 80e8, rate: 400},
    50  		{qty: 10e8, rate: 3000},
    51  	}
    52  	ob.update(bids, asks)
    53  
    54  	sortedBids := sortedEntries(true, bids)
    55  	sortedAsks := sortedEntries(false, asks)
    56  	snapBids, snapAsks := ob.snap()
    57  	if !reflect.DeepEqual(snapBids, sortedBids) {
    58  		t.Fatalf("wrong snap bids. expected %v got %v", sortedBids, snapBids)
    59  	}
    60  	if !reflect.DeepEqual(snapAsks, sortedAsks) {
    61  		t.Fatalf("wrong snap asks. expected %v got %v", sortedAsks, snapAsks)
    62  	}
    63  
    64  	type vwapFn func(bids bool, qty uint64) (vwap, extrema uint64, filled bool)
    65  	checkVWAPFn := func(fn vwapFn, bids bool, qty uint64, expVWAP, expExtrema uint64, expFilled bool) {
    66  		t.Helper()
    67  		vwap, extrema, filled := fn(bids, qty)
    68  		if filled != expFilled {
    69  			t.Fatalf("wrong filled. expected %v got %v", expFilled, filled)
    70  		}
    71  		if vwap != expVWAP {
    72  			t.Fatalf("wrong vwap. expected %d got %d", expVWAP, vwap)
    73  		}
    74  		if extrema != expExtrema {
    75  			t.Fatalf("wrong extrema. expected %d got %d", expExtrema, extrema)
    76  		}
    77  	}
    78  	checkVWAP := func(bids bool, qty uint64, expVWAP, expExtrema uint64, expFilled bool) {
    79  		t.Helper()
    80  		checkVWAPFn(ob.vwap, bids, qty, expVWAP, expExtrema, expFilled)
    81  	}
    82  
    83  	// Test vwap for bids and asks
    84  	expVWAP := (sortedBids[0].rate*30e8 + sortedBids[1].rate*30e8 + sortedBids[2].rate*5e8) / 65e8
    85  	checkVWAP(true, 65e8, expVWAP, 3000, true)
    86  	checkVWAP(false, 65e8, 400, 400, true)
    87  
    88  	// Test vwap for qty > total qty
    89  	checkVWAP(true, 161e8, 0, 0, false)
    90  	checkVWAP(false, 161e8, 0, 0, false)
    91  
    92  	// Update quantities. Setting qty to 0 should delete.
    93  	bids = []*obEntry{
    94  		{qty: 0, rate: 5000},
    95  		{qty: 50e8, rate: 4000},
    96  	}
    97  	asks = []*obEntry{
    98  		{qty: 0, rate: 400},
    99  		{qty: 35e8, rate: 4000},
   100  	}
   101  	ob.update(bids, asks)
   102  
   103  	// Make sure snap returns the correct entries
   104  	expSnapBids := []*obEntry{
   105  		{qty: 50e8, rate: 4000},
   106  		{qty: 10e8, rate: 3000},
   107  		{qty: 80e8, rate: 400},
   108  	}
   109  	expSnapAsks := []*obEntry{
   110  		{qty: 10e8, rate: 3000},
   111  		{qty: 35e8, rate: 4000},
   112  		{qty: 30e8, rate: 5000},
   113  	}
   114  	snapBids, snapAsks = ob.snap()
   115  	if !reflect.DeepEqual(snapBids, expSnapBids) {
   116  		t.Fatalf("wrong snap bids. expected %v got %v", expSnapBids, snapBids)
   117  	}
   118  	if !reflect.DeepEqual(snapAsks, expSnapAsks) {
   119  		t.Fatalf("wrong snap asks. expected %v got %v", expSnapAsks, snapAsks)
   120  	}
   121  
   122  	// Test vwap with updated quantities
   123  	expVWAP = (50e8*uint64(4000) + 10e8*uint64(3000) + 5e8*uint64(400)) / 65e8
   124  	checkVWAP(true, 65e8, expVWAP, 400, true)
   125  	expVWAP = (10e8*uint64(3000) + 35e8*uint64(4000) + 20e8*uint64(5000)) / 65e8
   126  	checkVWAP(false, 65e8, expVWAP, 5000, true)
   127  }
   128  
   129  // Test vwap with values that would overflow uint64.
   130  func TestVWAPOverflow(t *testing.T) {
   131  	bids := []*obEntry{
   132  		{qty: 1e15, rate: 12e9},
   133  		{qty: 1e15, rate: 10e9},
   134  	}
   135  	ob := newOrderBook()
   136  	ob.update(bids, nil)
   137  
   138  	vwap, extrema, filled := ob.vwap(true, 2e15)
   139  	if !filled {
   140  		t.Fatalf("should be filled")
   141  	}
   142  	if vwap != uint64(11e9) {
   143  		t.Fatalf("wrong vwap. expected %d got %d", uint64(11e9), vwap)
   144  	}
   145  	if extrema != uint64(10e9) {
   146  		t.Fatalf("wrong extrema")
   147  	}
   148  }