decred.org/dcrdex@v1.0.5/server/market/integ/booker_matcher_test.go (about)

     1  // Package integ_test is a black-box integration test package.
     2  // This file performs book-matcher integration tests.
     3  package integ_test
     4  
     5  import (
     6  	"math/rand"
     7  	"reflect"
     8  	"testing"
     9  	"time"
    10  
    11  	"decred.org/dcrdex/dex"
    12  	"decred.org/dcrdex/dex/order"
    13  	"decred.org/dcrdex/server/account"
    14  	"decred.org/dcrdex/server/book"
    15  	"decred.org/dcrdex/server/matcher"
    16  )
    17  
    18  // An arbitrary account ID for test orders.
    19  var acct0 = account.AccountID{
    20  	0x22, 0x4c, 0xba, 0xaa, 0xfa, 0x80, 0xbf, 0x3b,
    21  	0xd1, 0xff, 0x73, 0x15, 0x90, 0xbc, 0xbd, 0xda,
    22  	0x5a, 0x76, 0xf9, 0x1e, 0x60, 0xa1, 0x56, 0x99,
    23  	0x46, 0x34, 0xe9, 0x1c, 0xec, 0x25, 0xd5, 0x40, // 32 bytes
    24  }
    25  
    26  var rnd = rand.New(rand.NewSource(1))
    27  
    28  const (
    29  	AssetDCR uint32 = iota
    30  	AssetBTC
    31  
    32  	LotSize = uint64(10 * 1e8)
    33  )
    34  
    35  func startLogger() {
    36  	logger := dex.StdOutLogger("MATCHTEST - book", dex.LevelTrace)
    37  	book.UseLogger(logger)
    38  
    39  	logger = dex.StdOutLogger("MATCHTEST - matcher", dex.LevelTrace)
    40  	matcher.UseLogger(logger)
    41  }
    42  
    43  func randomPreimage() (pe order.Preimage) {
    44  	rnd.Read(pe[:])
    45  	return
    46  }
    47  
    48  func newLimitOrder(sell bool, rate, quantityLots uint64, force order.TimeInForce, timeOffset int64) *order.LimitOrder {
    49  	return newLimit(sell, rate, quantityLots, force, timeOffset).Order.(*order.LimitOrder)
    50  }
    51  
    52  func newLimit(sell bool, rate, quantityLots uint64, force order.TimeInForce, timeOffset int64) *matcher.OrderRevealed {
    53  	addr := "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui"
    54  	if sell {
    55  		addr = "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm"
    56  	}
    57  	pi := randomPreimage()
    58  	return &matcher.OrderRevealed{
    59  		Order: &order.LimitOrder{
    60  			P: order.Prefix{
    61  				AccountID:  acct0,
    62  				BaseAsset:  AssetDCR,
    63  				QuoteAsset: AssetBTC,
    64  				OrderType:  order.LimitOrderType,
    65  				ClientTime: time.Unix(1566497653+timeOffset, 0),
    66  				ServerTime: time.Unix(1566497656+timeOffset, 0),
    67  				Commit:     pi.Commit(),
    68  			},
    69  			T: order.Trade{
    70  				Coins:    []order.CoinID{},
    71  				Sell:     sell,
    72  				Quantity: quantityLots * LotSize,
    73  				Address:  addr,
    74  			},
    75  			Rate:  rate,
    76  			Force: force,
    77  		},
    78  		Preimage: pi,
    79  	}
    80  }
    81  
    82  func newMarketSellOrder(quantityLots uint64, timeOffset int64) *order.MarketOrder {
    83  	return newMarketSell(quantityLots, timeOffset).Order.(*order.MarketOrder)
    84  }
    85  
    86  func newMarketSell(quantityLots uint64, timeOffset int64) *matcher.OrderRevealed {
    87  	pi := randomPreimage()
    88  	return &matcher.OrderRevealed{
    89  		Order: &order.MarketOrder{
    90  			P: order.Prefix{
    91  				AccountID:  acct0,
    92  				BaseAsset:  AssetDCR,
    93  				QuoteAsset: AssetBTC,
    94  				OrderType:  order.MarketOrderType,
    95  				ClientTime: time.Unix(1566497653+timeOffset, 0),
    96  				ServerTime: time.Unix(1566497656+timeOffset, 0),
    97  				Commit:     pi.Commit(),
    98  			},
    99  			T: order.Trade{
   100  				Coins:    []order.CoinID{},
   101  				Sell:     true,
   102  				Quantity: quantityLots * LotSize,
   103  				Address:  "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm",
   104  			},
   105  		},
   106  		Preimage: pi,
   107  	}
   108  }
   109  
   110  func newMarketBuyOrder(quantityQuoteAsset uint64, timeOffset int64) *order.MarketOrder {
   111  	return newMarketBuy(quantityQuoteAsset, timeOffset).Order.(*order.MarketOrder)
   112  }
   113  
   114  func newMarketBuy(quantityQuoteAsset uint64, timeOffset int64) *matcher.OrderRevealed {
   115  	pi := randomPreimage()
   116  	return &matcher.OrderRevealed{
   117  		Order: &order.MarketOrder{
   118  			P: order.Prefix{
   119  				AccountID:  acct0,
   120  				BaseAsset:  AssetDCR,
   121  				QuoteAsset: AssetBTC,
   122  				OrderType:  order.MarketOrderType,
   123  				ClientTime: time.Unix(1566497653+timeOffset, 0),
   124  				ServerTime: time.Unix(1566497656+timeOffset, 0),
   125  				Commit:     pi.Commit(),
   126  			},
   127  			T: order.Trade{
   128  				Coins:    []order.CoinID{},
   129  				Sell:     false,
   130  				Quantity: quantityQuoteAsset,
   131  				Address:  "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui",
   132  			},
   133  		},
   134  		Preimage: pi,
   135  	}
   136  }
   137  
   138  func newCancel(targetOrderID order.OrderID, serverTime time.Time) *matcher.OrderRevealed {
   139  	pi := randomPreimage()
   140  	return &matcher.OrderRevealed{
   141  		Order: &order.CancelOrder{
   142  			P: order.Prefix{
   143  				ServerTime: serverTime,
   144  				Commit:     pi.Commit(),
   145  			},
   146  			TargetOrderID: targetOrderID,
   147  		},
   148  		Preimage: pi,
   149  	}
   150  }
   151  
   152  func newCancelOrder(targetOrderID order.OrderID, serverTime time.Time) *order.CancelOrder {
   153  	return newCancel(targetOrderID, serverTime).Order.(*order.CancelOrder)
   154  }
   155  
   156  var (
   157  	// Create a coherent order book of standing orders and sorted rates.
   158  	bookBuyOrders = []*order.LimitOrder{
   159  		newLimitOrder(false, 2500000, 2, order.StandingTiF, 0),
   160  		newLimitOrder(false, 2700000, 2, order.StandingTiF, 0),
   161  		newLimitOrder(false, 3200000, 2, order.StandingTiF, 0),
   162  		newLimitOrder(false, 3300000, 1, order.StandingTiF, 2), // newer
   163  		newLimitOrder(false, 3300000, 2, order.StandingTiF, 0), // older
   164  		newLimitOrder(false, 3600000, 4, order.StandingTiF, 0),
   165  		newLimitOrder(false, 3900000, 2, order.StandingTiF, 0),
   166  		newLimitOrder(false, 4000000, 10, order.StandingTiF, 0),
   167  		newLimitOrder(false, 4300000, 4, order.StandingTiF, 1), // newer
   168  		newLimitOrder(false, 4300000, 2, order.StandingTiF, 0), // older
   169  		newLimitOrder(false, 4500000, 1, order.StandingTiF, 0),
   170  	}
   171  	bookSellOrders = []*order.LimitOrder{
   172  		newLimitOrder(true, 6200000, 2, order.StandingTiF, 1), // newer
   173  		newLimitOrder(true, 6200000, 2, order.StandingTiF, 0), // older
   174  		newLimitOrder(true, 6100000, 2, order.StandingTiF, 0),
   175  		newLimitOrder(true, 6000000, 2, order.StandingTiF, 0),
   176  		newLimitOrder(true, 5500000, 1, order.StandingTiF, 0),
   177  		newLimitOrder(true, 5400000, 4, order.StandingTiF, 0),
   178  		newLimitOrder(true, 5000000, 2, order.StandingTiF, 0),
   179  		newLimitOrder(true, 4700000, 4, order.StandingTiF, 1),  // newer
   180  		newLimitOrder(true, 4700000, 10, order.StandingTiF, 0), // older
   181  		newLimitOrder(true, 4600000, 2, order.StandingTiF, 0),
   182  		newLimitOrder(true, 4550000, 1, order.StandingTiF, 0),
   183  	}
   184  )
   185  
   186  const (
   187  	bookBuyLots   = 32
   188  	bookSellLots  = 32
   189  	initialMidGap = (4550000 + 4500000) / 2
   190  )
   191  
   192  func newBook(t *testing.T) *book.Book {
   193  	resetMakers()
   194  
   195  	b := book.New(LotSize, 0)
   196  
   197  	for _, o := range bookBuyOrders {
   198  		if ok := b.Insert(o); !ok {
   199  			t.Fatalf("Failed to insert buy order %v", o)
   200  		}
   201  	}
   202  	for _, o := range bookSellOrders {
   203  		if ok := b.Insert(o); !ok {
   204  			t.Fatalf("Failed to insert sell order %v", o)
   205  		}
   206  	}
   207  	return b
   208  }
   209  
   210  func resetMakers() {
   211  	for _, o := range bookBuyOrders {
   212  		o.FillAmt = 0
   213  	}
   214  	for _, o := range bookSellOrders {
   215  		o.FillAmt = 0
   216  	}
   217  }
   218  
   219  func newMatchSet(taker order.Order, makers []*order.LimitOrder, lastPartialAmount ...uint64) *order.MatchSet {
   220  	amounts := make([]uint64, len(makers))
   221  	rates := make([]uint64, len(makers))
   222  	var total uint64
   223  	for i := range makers {
   224  		total += makers[i].Quantity
   225  		amounts[i] = makers[i].Quantity
   226  		rates[i] = makers[i].Rate
   227  	}
   228  	if len(lastPartialAmount) > 0 {
   229  		amounts[len(makers)-1] = lastPartialAmount[0]
   230  		total -= makers[len(makers)-1].Quantity - lastPartialAmount[0]
   231  	}
   232  	return &order.MatchSet{
   233  		Taker:   taker,
   234  		Makers:  makers,
   235  		Amounts: amounts,
   236  		Rates:   rates,
   237  		Total:   total,
   238  	}
   239  }
   240  
   241  func TestMatchWithBook_limitsOnly(t *testing.T) {
   242  	// Setup the match package's logger.
   243  	startLogger()
   244  
   245  	// New matching engine.
   246  	me := matcher.New()
   247  
   248  	rnd.Seed(0)
   249  
   250  	badLotsizeOrder := newLimit(false, 05000000, 1, order.ImmediateTiF, 0)
   251  	badLotsizeOrder.Order.(*order.LimitOrder).Quantity /= 2
   252  
   253  	// takers is heterogenous w.r.t. type
   254  	takers := []*matcher.OrderRevealed{
   255  		newLimit(false, 4550000, 1, order.ImmediateTiF, 0), // buy, 1 lot, immediate, equal rate
   256  		newLimit(false, 4550000, 2, order.StandingTiF, 0),  // buy, 2 lot, standing, equal rate, partial taker insert to book
   257  		newLimit(false, 4550000, 2, order.ImmediateTiF, 0), // buy, 2 lot, immediate, equal rate, partial taker unfilled
   258  		newLimit(false, 4100000, 1, order.ImmediateTiF, 0), // buy, 1 lot, immediate, unfilled fail
   259  		newLimit(true, 4540000, 1, order.ImmediateTiF, 0),  // sell, 1 lot, immediate
   260  		newLimit(true, 4300000, 4, order.ImmediateTiF, 0),  // sell, 4 lot, immediate, partial maker
   261  	}
   262  
   263  	// tweak taker[4] commitment to get desired order.
   264  	takers[4].Preimage[0] += 0 // brute forced, could have required multiple bytes changed
   265  	takers[4].Order.(*order.LimitOrder).Commit = takers[4].Preimage.Commit()
   266  
   267  	resetTakers := func() {
   268  		for _, o := range takers {
   269  			switch ot := o.Order.(type) {
   270  			case *order.MarketOrder:
   271  				ot.FillAmt = 0
   272  			case *order.LimitOrder:
   273  				ot.FillAmt = 0
   274  			}
   275  		}
   276  	}
   277  
   278  	nSell := len(bookSellOrders)
   279  	nBuy := len(bookBuyOrders)
   280  
   281  	type args struct {
   282  		book  *book.Book
   283  		queue []*matcher.OrderRevealed
   284  	}
   285  	tests := []struct {
   286  		name             string
   287  		args             args
   288  		doesMatch        bool
   289  		wantMatches      []*order.MatchSet
   290  		wantNumPassed    int
   291  		wantNumFailed    int
   292  		wantDoneOK       int
   293  		wantNumPartial   int
   294  		wantNumBooked    int
   295  		wantNumUnbooked  int
   296  		wantNumNomatched int
   297  	}{
   298  		{
   299  			name: "limit buy immediate rate match",
   300  			args: args{
   301  				book:  newBook(t),
   302  				queue: []*matcher.OrderRevealed{takers[0]},
   303  			},
   304  			doesMatch: true,
   305  			wantMatches: []*order.MatchSet{
   306  				newMatchSet(takers[0].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   307  			},
   308  			wantNumPassed:    1,
   309  			wantNumFailed:    0,
   310  			wantDoneOK:       1,
   311  			wantNumPartial:   0,
   312  			wantNumBooked:    0,
   313  			wantNumUnbooked:  1,
   314  			wantNumNomatched: 0,
   315  		},
   316  		{
   317  			name: "limit buy standing partial taker inserted to book",
   318  			args: args{
   319  				book:  newBook(t),
   320  				queue: []*matcher.OrderRevealed{takers[1]},
   321  			},
   322  			doesMatch: true,
   323  			wantMatches: []*order.MatchSet{
   324  				newMatchSet(takers[1].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   325  			},
   326  			wantNumPassed:    1,
   327  			wantNumFailed:    0,
   328  			wantDoneOK:       0,
   329  			wantNumPartial:   1,
   330  			wantNumBooked:    1,
   331  			wantNumUnbooked:  1,
   332  			wantNumNomatched: 0,
   333  		},
   334  		{
   335  			name: "limit buy immediate partial taker unfilled",
   336  			args: args{
   337  				book:  newBook(t),
   338  				queue: []*matcher.OrderRevealed{takers[2]},
   339  			},
   340  			doesMatch: true,
   341  			wantMatches: []*order.MatchSet{
   342  				newMatchSet(takers[2].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   343  			},
   344  			wantNumPassed:    1,
   345  			wantNumFailed:    0,
   346  			wantDoneOK:       1,
   347  			wantNumPartial:   1,
   348  			wantNumBooked:    0,
   349  			wantNumUnbooked:  1,
   350  			wantNumNomatched: 0,
   351  		},
   352  		{
   353  			name: "limit buy immediate unfilled fail",
   354  			args: args{
   355  				book:  newBook(t),
   356  				queue: []*matcher.OrderRevealed{takers[3]},
   357  			},
   358  			doesMatch:        false,
   359  			wantMatches:      nil,
   360  			wantNumPassed:    0,
   361  			wantNumFailed:    1,
   362  			wantDoneOK:       0,
   363  			wantNumPartial:   0,
   364  			wantNumBooked:    0,
   365  			wantNumUnbooked:  0,
   366  			wantNumNomatched: 1,
   367  		},
   368  		{
   369  			name: "bad lot size order",
   370  			args: args{
   371  				book:  newBook(t),
   372  				queue: []*matcher.OrderRevealed{badLotsizeOrder},
   373  			},
   374  			doesMatch:        false,
   375  			wantMatches:      nil,
   376  			wantNumPassed:    0,
   377  			wantNumFailed:    1,
   378  			wantDoneOK:       0,
   379  			wantNumPartial:   0,
   380  			wantNumBooked:    0,
   381  			wantNumUnbooked:  0,
   382  			wantNumNomatched: 0,
   383  		},
   384  		{
   385  			name: "limit buy standing partial taker inserted to book, then filled by down-queue sell",
   386  			args: args{
   387  				book:  newBook(t),
   388  				queue: []*matcher.OrderRevealed{takers[1], takers[4]},
   389  			},
   390  			doesMatch: true,
   391  			wantMatches: []*order.MatchSet{
   392  				newMatchSet(takers[1].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   393  				{ // the maker is reduced by matching first item in the queue
   394  					Taker:   takers[4].Order,
   395  					Makers:  []*order.LimitOrder{takers[1].Order.(*order.LimitOrder)},
   396  					Amounts: []uint64{1 * LotSize}, // 2 - 1
   397  					Rates:   []uint64{4550000},
   398  					Total:   1 * LotSize,
   399  				},
   400  			},
   401  			wantNumPassed:    2,
   402  			wantNumFailed:    0,
   403  			wantDoneOK:       1,
   404  			wantNumPartial:   1,
   405  			wantNumBooked:    1,
   406  			wantNumUnbooked:  2,
   407  			wantNumNomatched: 0,
   408  		},
   409  		{
   410  			name: "limit sell immediate rate overlap",
   411  			args: args{
   412  				book:  newBook(t),
   413  				queue: []*matcher.OrderRevealed{takers[5]},
   414  			},
   415  			doesMatch: true,
   416  			wantMatches: []*order.MatchSet{
   417  				newMatchSet(
   418  					takers[5].Order,
   419  					[]*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2], bookBuyOrders[nBuy-3]},
   420  					1*LotSize),
   421  			},
   422  			wantNumPassed:    1,
   423  			wantNumFailed:    0,
   424  			wantDoneOK:       1,
   425  			wantNumPartial:   0,
   426  			wantNumBooked:    0,
   427  			wantNumUnbooked:  2,
   428  			wantNumNomatched: 0,
   429  		},
   430  	}
   431  	for _, tt := range tests {
   432  		t.Run(tt.name, func(t *testing.T) {
   433  			// Reset Filled amounts of all pre-defined orders before each test.
   434  			resetTakers()
   435  			resetMakers()
   436  
   437  			// Ignore the seed since it is tested in the matcher unit tests.
   438  			_, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, _ := me.Match(tt.args.book, tt.args.queue)
   439  			matchMade := len(matches) > 0 && matches[0] != nil
   440  			if tt.doesMatch != matchMade {
   441  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
   442  			}
   443  			if len(matches) != len(tt.wantMatches) {
   444  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
   445  			}
   446  			for i := range matches {
   447  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
   448  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
   449  				}
   450  			}
   451  			if len(passed) != tt.wantNumPassed {
   452  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
   453  			}
   454  			if len(failed) != tt.wantNumFailed {
   455  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
   456  			}
   457  			if len(doneOK) != tt.wantDoneOK {
   458  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
   459  			}
   460  			if len(partial) != tt.wantNumPartial {
   461  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
   462  			}
   463  			if len(booked) != tt.wantNumBooked {
   464  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked)
   465  			}
   466  			if len(unbooked) != tt.wantNumUnbooked {
   467  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
   468  			}
   469  
   470  			if len(nomatched) != tt.wantNumNomatched {
   471  				t.Errorf("number nomatched %d, expected %d", len(nomatched), tt.wantNumNomatched)
   472  			}
   473  		})
   474  	}
   475  }
   476  
   477  func orderInSlice(o *matcher.OrderRevealed, s []*matcher.OrderRevealed) int {
   478  	for i := range s {
   479  		if s[i].Order.ID() == o.Order.ID() {
   480  			return i
   481  		}
   482  	}
   483  	return -1
   484  }
   485  
   486  func orderInLimitSlice(o order.Order, s []*order.LimitOrder) int {
   487  	for i := range s {
   488  		if s[i].ID() == o.ID() {
   489  			return i
   490  		}
   491  	}
   492  	return -1
   493  }
   494  
   495  func TestMatchWithBook_limitsOnly_multipleQueued(t *testing.T) {
   496  	// Setup the match package's logger.
   497  	startLogger()
   498  
   499  	// New matching engine.
   500  	me := matcher.New()
   501  
   502  	rnd.Seed(0)
   503  
   504  	// epochQueue is heterogenous w.r.t. type
   505  	epochQueue := []*matcher.OrderRevealed{
   506  		// buys
   507  		newLimit(false, 4550000, 1, order.ImmediateTiF, 0), // 0: buy, 1 lot, immediate
   508  		newLimit(false, 4550000, 2, order.StandingTiF, 0),  // 1: buy, 2 lot, standing
   509  		newLimit(false, 4550000, 2, order.ImmediateTiF, 0), // 2: buy, 2 lot, immediate
   510  		newLimit(false, 4100000, 1, order.ImmediateTiF, 0), // 3: buy, 1 lot, immediate
   511  		// sells
   512  		newLimit(true, 4540000, 1, order.ImmediateTiF, 0), // 4: sell, 1 lot, immediate
   513  		newLimit(true, 4300000, 4, order.ImmediateTiF, 0), // 5: sell, 4 lot, immediate
   514  		newLimit(true, 4720000, 40, order.StandingTiF, 0), // 6: sell, 40 lot, standing, unfilled insert
   515  	}
   516  	epochQueue[0].Preimage = order.Preimage{
   517  		0xb1, 0xcb, 0x0a, 0xc8, 0xbf, 0x2b, 0xa9, 0xa7,
   518  		0x05, 0xf9, 0x6d, 0x6b, 0x68, 0x21, 0x28, 0x87,
   519  		0x13, 0x26, 0x23, 0x80, 0xfb, 0xe6, 0xb9, 0x0f,
   520  		0x74, 0x39, 0xc9, 0xf1, 0xcd, 0x6e, 0x02, 0xa8}
   521  	epochQueue[0].Order.(*order.LimitOrder).Commit = epochQueue[0].Preimage.Commit()
   522  	epochQueueInit := make([]*matcher.OrderRevealed, len(epochQueue))
   523  	copy(epochQueueInit, epochQueue)
   524  
   525  	/* //brute force a commitment to make changing the test less horrible
   526  	t.Log(epochQueue)
   527  	matcher.ShuffleQueue(epochQueue)
   528  
   529  	// Apply the shuffling to determine matching order that will be used.
   530  	wantOrder := []int{1, 6, 0, 3, 4, 5, 2}
   531  	var wantQueue []*matcher.OrderRevealed
   532  	for _, i := range wantOrder {
   533  		wantQueue = append(wantQueue, epochQueueInit[i])
   534  	}
   535  
   536  	queuesEqual := func(q1, q2 []*matcher.OrderRevealed) bool {
   537  		if len(q1) != len(q2) {
   538  			return false
   539  		}
   540  		for i := range q1 {
   541  			if q1[i].Order.(*order.LimitOrder) != q2[i].Order.(*order.LimitOrder) {
   542  				return false
   543  			}
   544  		}
   545  		return true
   546  	}
   547  
   548  	orderX := epochQueueInit[0]
   549  	loX := orderX.Order.(*order.LimitOrder)
   550  	var pi order.Preimage
   551  	var i int
   552  	for !queuesEqual(wantQueue, epochQueue) {
   553  		pi = randomPreimage()
   554  		orderX.Preimage = pi
   555  		loX.Commit = pi.Commit()
   556  		loX.SetTime(loX.ServerTime) // force recomputation of order ID
   557  		matcher.ShuffleQueue(epochQueue)
   558  		i++
   559  	}
   560  	t.Logf("preimage: %#v, commit: %#v", pi, loX.Commit)
   561  	t.Log(i, epochQueue)
   562  	*/
   563  
   564  	// -> Shuffles to [1, 6, 0, 3, 4, 5, 2]
   565  	// 1 -> partial match, inserted into book (passed, partial inserted), 1 lot @ 4550000, buyvol + 1, sellvol - 1 = 0
   566  	// 6 -> unmatched, inserted into book (inserted, nomatched), sellvol + 40
   567  	// 0 -> is unfilled (failed, nomatched)
   568  	// 3 -> is unfilled (failed, nomatched)
   569  	// 4 -> fills against order 1, which was just inserted (passed), 1 lot @ 4550000, buyvol - 1
   570  	// 5 -> is filled (passed) 1 @ 4500000, 3 @ 4300000, buyvol - 4
   571  	// 2 -> is unfilled (failed, nomatched)
   572  	// matches: [1, 4, 5], passed: [1, 4], failed: [0, 3, 2]
   573  	// partial: [1], inserted: [1, 6], nomatched: [6, 0, 3, 2]
   574  
   575  	// best remaining sell: 4600000, buy: 4300000
   576  
   577  	// order book from bookBuyOrders and bookSellOrders
   578  	b := newBook(t)
   579  
   580  	resetQueue := func() {
   581  		for _, o := range epochQueue {
   582  			switch ot := o.Order.(type) {
   583  			case *order.MarketOrder:
   584  				ot.FillAmt = 0
   585  			case *order.LimitOrder:
   586  				ot.FillAmt = 0
   587  			}
   588  		}
   589  	}
   590  
   591  	// nSell := len(bookSellOrders)
   592  	// nBuy := len(bookBuyOrders)
   593  
   594  	// Reset Filled amounts of all pre-defined orders before each test.
   595  	resetQueue()
   596  	resetMakers()
   597  
   598  	// Ignore the seed since it is tested in the matcher unit tests.
   599  	_, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, stats := me.Match(b, epochQueue)
   600  	//t.Log(matches, passed, failed, doneOK, partial, booked, unbooked)
   601  
   602  	lastMatch := matches[len(matches)-1]
   603  
   604  	compareMatchStats(t, &matcher.MatchCycleStats{
   605  		BookBuys:    (bookBuyLots - 4) * LotSize,
   606  		BookSells:   (bookSellLots + 39) * LotSize,
   607  		MatchVolume: 6 * LotSize,
   608  		HighRate:    4550000,
   609  		LowRate:     4300000,
   610  		StartRate:   matches[0].Makers[0].Rate,
   611  		EndRate:     lastMatch.Makers[len(lastMatch.Makers)-1].Rate,
   612  	}, stats)
   613  
   614  	// PASSED orders
   615  
   616  	// epoch order 0 should be order 0 in passed slice
   617  	expectedLoc := 0
   618  	if loc := orderInSlice(epochQueueInit[1], passed); loc == -1 {
   619  		t.Errorf("Order not in passed slice.")
   620  	} else if loc != expectedLoc {
   621  		t.Errorf("Order not at expected location in passed slice: %d", loc)
   622  	}
   623  
   624  	// epoch order 5 should be order 2 in passed slice
   625  	expectedLoc = 2
   626  	if loc := orderInSlice(epochQueueInit[4], passed); loc == -1 {
   627  		t.Errorf("Order not in passed slice.")
   628  	} else if loc != expectedLoc {
   629  		t.Errorf("Order not at expected location in passed slice: %d", loc)
   630  	}
   631  
   632  	//t.Log(doneOK)
   633  
   634  	// FAILED orders
   635  
   636  	// epoch order 3 should be order 0 in failed slice
   637  	expectedLoc = 0
   638  	if loc := orderInSlice(epochQueueInit[0], failed); loc == -1 {
   639  		t.Errorf("Order not in failed slice.")
   640  	} else if loc != expectedLoc {
   641  		t.Errorf("Order not at expected location in failed slice: %d", loc)
   642  	}
   643  
   644  	// epoch order 4 should be order 1 in failed slice
   645  	expectedLoc = 1
   646  	if loc := orderInSlice(epochQueueInit[3], failed); loc == -1 {
   647  		t.Errorf("Order not in failed slice.")
   648  	} else if loc != expectedLoc {
   649  		t.Errorf("Order not at expected location in failed slice: %d", loc)
   650  	}
   651  
   652  	// epoch order 2 should be order 2 in failed slice
   653  	expectedLoc = 2
   654  	if loc := orderInSlice(epochQueueInit[2], failed); loc == -1 {
   655  		t.Errorf("Order not in failed slice.")
   656  	} else if loc != expectedLoc {
   657  		t.Errorf("Order not at expected location in failed slice: %d", loc)
   658  	}
   659  
   660  	// Done OK
   661  	expectedLoc = 1
   662  	if loc := orderInSlice(epochQueueInit[5], doneOK); loc == -1 {
   663  		t.Errorf("Order not in doneOK slice.")
   664  	} else if loc != expectedLoc {
   665  		t.Errorf("Order not at expected location in doneOK slice: %d", loc)
   666  	}
   667  
   668  	// PARTIAL fills
   669  
   670  	// epoch order 1 should be order 0 in partial slice
   671  	expectedLoc = 0
   672  	if loc := orderInSlice(epochQueueInit[1], partial); loc == -1 {
   673  		t.Errorf("Order not in partial slice.")
   674  	} else if loc != expectedLoc {
   675  		t.Errorf("Order not at expected location in partial slice: %d", loc)
   676  	}
   677  
   678  	// BOOKED orders
   679  
   680  	// epoch order 1 should be order 0 in booked slice
   681  	expectedLoc = 0
   682  	if loc := orderInSlice(epochQueueInit[1], booked); loc == -1 {
   683  		t.Errorf("Order not in booked slice.")
   684  	} else if loc != expectedLoc {
   685  		t.Errorf("Order not at expected location in booked slice: %d", loc)
   686  	}
   687  
   688  	// epoch order 6 should be order 1 in booked slice
   689  	expectedLoc = 1
   690  	if loc := orderInSlice(epochQueueInit[6], booked); loc == -1 {
   691  		t.Errorf("Order not in booked slice.")
   692  	} else if loc != expectedLoc {
   693  		t.Errorf("Order not at expected location in booked slice: %d", loc)
   694  	}
   695  
   696  	// epoch order 1 should be order 1 in unbooked slice
   697  	expectedLoc = 1
   698  	if loc := orderInLimitSlice(epochQueueInit[1].Order, unbooked); loc == -1 {
   699  		t.Errorf("Order not in unbooked slice.")
   700  	} else if loc != expectedLoc {
   701  		t.Errorf("Order not at expected location in unbooked slice: %d", loc)
   702  	}
   703  
   704  	// NOMATCHED orders
   705  
   706  	// We don't know the exact order, since there is an intermediate map used
   707  	// for tracking.
   708  	if len(nomatched) != 4 {
   709  		t.Errorf("Wrong number of nomatched orders. Wanted 4, got %d", len(nomatched))
   710  	} else {
   711  		for _, i := range []int{6, 0, 3, 2} {
   712  			if orderInSlice(epochQueueInit[i], nomatched) == -1 {
   713  				t.Errorf("Epoch queue order %d not in nomatched slice", i)
   714  			}
   715  		}
   716  	}
   717  
   718  	// epoch order 5 (sell, 4 lots, immediate @ 4300000) is match 1, matched
   719  	// with 3 orders, the first of which of which is epoch order 1 (buy, 2 lots,
   720  	// standing @ 4550000) that was booked as a standing order.
   721  	if matches[1].Taker.ID() != epochQueueInit[4].Order.ID() {
   722  		t.Errorf("Taker order ID expected %v, got %v",
   723  			epochQueueInit[5].Order.UID(), matches[1].Taker.UID())
   724  	}
   725  	if matches[1].Makers[0].ID() != epochQueueInit[1].Order.ID() {
   726  		t.Errorf("First match was expected to be %v, got %v",
   727  			epochQueueInit[1].Order.ID(), matches[1].Makers[0].ID())
   728  	}
   729  }
   730  
   731  func TestMatch_cancelOnly(t *testing.T) {
   732  	// Setup the match package's logger.
   733  	startLogger()
   734  
   735  	// New matching engine.
   736  	me := matcher.New()
   737  
   738  	rnd.Seed(0)
   739  
   740  	fakeOrder := newLimitOrder(false, 4550000, 1, order.ImmediateTiF, 0)
   741  	fakeOrder.ServerTime = time.Unix(1566497654, 0)
   742  
   743  	// takers is heterogenous w.r.t. type
   744  	takers := []*matcher.OrderRevealed{
   745  		newCancel(bookBuyOrders[3].ID(), fakeOrder.ServerTime.Add(time.Second)),
   746  		newCancel(fakeOrder.ID(), fakeOrder.ServerTime.Add(time.Second)),
   747  	}
   748  
   749  	//nSell := len(bookSellOrders)
   750  	//nBuy := len(bookBuyOrders)
   751  
   752  	type args struct {
   753  		book  *book.Book
   754  		queue []*matcher.OrderRevealed
   755  	}
   756  	tests := []struct {
   757  		name            string
   758  		args            args
   759  		doesMatch       bool
   760  		wantMatches     []*order.MatchSet
   761  		wantNumPassed   int
   762  		wantNumFailed   int
   763  		wantDoneOK      int
   764  		wantNumPartial  int
   765  		wantNumBooked   int
   766  		wantNumUnbooked int
   767  	}{
   768  		{
   769  			name: "cancel standing ok",
   770  			args: args{
   771  				book:  newBook(t),
   772  				queue: []*matcher.OrderRevealed{takers[0]},
   773  			},
   774  			doesMatch: true,
   775  			wantMatches: []*order.MatchSet{
   776  				{
   777  					Taker:   takers[0].Order,
   778  					Makers:  []*order.LimitOrder{bookBuyOrders[3]},
   779  					Amounts: []uint64{bookBuyOrders[3].Remaining()},
   780  					Rates:   []uint64{bookBuyOrders[3].Rate},
   781  				},
   782  			},
   783  			wantNumPassed:   1,
   784  			wantNumFailed:   0,
   785  			wantDoneOK:      1,
   786  			wantNumPartial:  0,
   787  			wantNumBooked:   0,
   788  			wantNumUnbooked: 1,
   789  		},
   790  		{
   791  			name: "cancel non-existent standing",
   792  			args: args{
   793  				book:  newBook(t),
   794  				queue: []*matcher.OrderRevealed{takers[1]},
   795  			},
   796  			doesMatch:       false,
   797  			wantMatches:     nil,
   798  			wantNumPassed:   0,
   799  			wantNumFailed:   1,
   800  			wantDoneOK:      0,
   801  			wantNumPartial:  0,
   802  			wantNumBooked:   0,
   803  			wantNumUnbooked: 0,
   804  		},
   805  	}
   806  	for _, tt := range tests {
   807  		t.Run(tt.name, func(t *testing.T) {
   808  			// Reset Filled amounts of all pre-defined orders before each test.
   809  			resetMakers()
   810  
   811  			// var cancels int
   812  			// for _, oi := range tt.args.queue {
   813  			// 	if oi.Type() == order.CancelOrderType {
   814  			// 		cancels++
   815  			// 	}
   816  			// }
   817  
   818  			numBuys0 := tt.args.book.BuyCount()
   819  
   820  			// Ignore the seed since it is tested in the matcher unit tests.
   821  			_, matches, passed, failed, doneOK, partial, booked, _, unbooked, _, _ := me.Match(tt.args.book, tt.args.queue)
   822  			matchMade := len(matches) > 0 && matches[0] != nil
   823  			if tt.doesMatch != matchMade {
   824  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
   825  			}
   826  			if len(matches) != len(tt.wantMatches) {
   827  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
   828  			}
   829  			for i := range matches {
   830  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
   831  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
   832  				}
   833  			}
   834  			if len(passed) != tt.wantNumPassed {
   835  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
   836  			}
   837  			if len(failed) != tt.wantNumFailed {
   838  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
   839  			}
   840  			if len(doneOK) != tt.wantDoneOK {
   841  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
   842  			}
   843  			if len(partial) != tt.wantNumPartial {
   844  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
   845  			}
   846  			if len(booked) != tt.wantNumBooked {
   847  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked)
   848  			}
   849  			if len(unbooked) != tt.wantNumUnbooked {
   850  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
   851  			}
   852  
   853  			numBuys1 := tt.args.book.BuyCount()
   854  			if numBuys0-len(passed) != numBuys1 {
   855  				t.Errorf("Buy side order book size %d, expected %d", numBuys1, numBuys0-len(passed))
   856  			}
   857  		})
   858  	}
   859  }
   860  
   861  func TestMatch_marketSellsOnly(t *testing.T) {
   862  	// Setup the match package's logger.
   863  	startLogger()
   864  
   865  	// New matching engine.
   866  	me := matcher.New()
   867  
   868  	rnd.Seed(0)
   869  
   870  	badLotsizeOrder := newMarketSell(1, 0)
   871  	badLotsizeOrder.Order.(*order.MarketOrder).Quantity /= 2
   872  
   873  	// takers is heterogenous w.r.t. type
   874  	takers := []*matcher.OrderRevealed{
   875  		newMarketSell(1, 0),  // sell, 1 lot
   876  		newMarketSell(3, 0),  // sell, 3 lot
   877  		newMarketSell(5, 0),  // sell, 5 lot, partial maker fill
   878  		newMarketSell(99, 0), // sell, 99 lot, partial taker fill
   879  	}
   880  
   881  	resetTakers := func() {
   882  		for _, o := range takers {
   883  			switch ot := o.Order.(type) {
   884  			case *order.MarketOrder:
   885  				ot.FillAmt = 0
   886  			case *order.LimitOrder:
   887  				ot.FillAmt = 0
   888  			}
   889  		}
   890  	}
   891  
   892  	//nSell := len(bookSellOrders)
   893  	nBuy := len(bookBuyOrders)
   894  
   895  	type args struct {
   896  		book  *book.Book
   897  		queue []*matcher.OrderRevealed
   898  	}
   899  	tests := []struct {
   900  		name             string
   901  		args             args
   902  		doesMatch        bool
   903  		wantMatches      []*order.MatchSet
   904  		wantNumPassed    int
   905  		wantNumFailed    int
   906  		wantDoneOK       int
   907  		wantNumPartial   int
   908  		wantNumBooked    int
   909  		wantNumUnbooked  int
   910  		wantNumNomatched int
   911  		matchStats       *matcher.MatchCycleStats
   912  	}{
   913  		{
   914  			name: "market sell, 1 maker match",
   915  			args: args{
   916  				book:  newBook(t),
   917  				queue: []*matcher.OrderRevealed{takers[0]},
   918  			},
   919  			doesMatch: true,
   920  			wantMatches: []*order.MatchSet{
   921  				// 1 lot @ 4500000. Leaves best buy of 4300000 behind.
   922  				newMatchSet(takers[0].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1]}),
   923  			},
   924  			wantNumPassed:    1,
   925  			wantNumFailed:    0,
   926  			wantDoneOK:       1,
   927  			wantNumPartial:   0,
   928  			wantNumBooked:    0,
   929  			wantNumUnbooked:  1,
   930  			wantNumNomatched: 0,
   931  			matchStats: &matcher.MatchCycleStats{
   932  				BookBuys:    (bookBuyLots - 1) * LotSize,
   933  				BookSells:   bookSellLots * LotSize,
   934  				MatchVolume: LotSize,
   935  				HighRate:    bookBuyOrders[nBuy-1].Rate,
   936  				LowRate:     bookBuyOrders[nBuy-1].Rate,
   937  				StartRate:   bookBuyOrders[nBuy-1].Rate,
   938  				EndRate:     bookBuyOrders[nBuy-1].Rate,
   939  			},
   940  		},
   941  		{
   942  			name: "market sell, 2 maker match",
   943  			args: args{
   944  				book:  newBook(t),
   945  				queue: []*matcher.OrderRevealed{takers[1]}, // 3 lots
   946  			},
   947  			doesMatch: true,
   948  			wantMatches: []*order.MatchSet{
   949  				newMatchSet(takers[1].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2]}),
   950  			},
   951  			wantNumPassed:    1,
   952  			wantNumFailed:    0,
   953  			wantDoneOK:       1,
   954  			wantNumPartial:   0,
   955  			wantNumBooked:    0,
   956  			wantNumUnbooked:  2,
   957  			wantNumNomatched: 0,
   958  			matchStats: &matcher.MatchCycleStats{
   959  				BookBuys:    (bookBuyLots - 3) * LotSize,
   960  				BookSells:   bookSellLots * LotSize,
   961  				MatchVolume: 3 * LotSize,
   962  				HighRate:    bookBuyOrders[nBuy-1].Rate,
   963  				LowRate:     bookBuyOrders[nBuy-2].Rate,
   964  				StartRate:   bookBuyOrders[nBuy-1].Rate,
   965  				EndRate:     bookBuyOrders[nBuy-2].Rate,
   966  			},
   967  			// newLimitOrder(false, 4300000, 2, order.StandingTiF, 0), // older
   968  			// newLimitOrder(false, 4500000, 1, order.StandingTiF, 0),
   969  		},
   970  		{
   971  			name: "market sell, 2 maker match, partial maker fill",
   972  			args: args{
   973  				book:  newBook(t),
   974  				queue: []*matcher.OrderRevealed{takers[2]},
   975  			},
   976  			doesMatch: true,
   977  			wantMatches: []*order.MatchSet{
   978  				// 1 lot @ 4500000, 4 @ 4300000
   979  				newMatchSet(takers[2].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2], bookBuyOrders[nBuy-3]}, 2*LotSize),
   980  			},
   981  			wantNumPassed:    1,
   982  			wantNumFailed:    0,
   983  			wantDoneOK:       1,
   984  			wantNumPartial:   0,
   985  			wantNumBooked:    0,
   986  			wantNumUnbooked:  2,
   987  			wantNumNomatched: 0,
   988  			matchStats: &matcher.MatchCycleStats{
   989  
   990  				BookBuys:    (bookBuyLots - 5) * LotSize,
   991  				BookSells:   bookSellLots * LotSize,
   992  				MatchVolume: 5 * LotSize,
   993  
   994  				HighRate:  bookBuyOrders[nBuy-1].Rate,
   995  				LowRate:   bookBuyOrders[nBuy-3].Rate,
   996  				StartRate: bookBuyOrders[nBuy-1].Rate,
   997  				EndRate:   bookBuyOrders[nBuy-3].Rate,
   998  			},
   999  		},
  1000  		{
  1001  			name: "market sell bad lot size",
  1002  			args: args{
  1003  				book:  newBook(t),
  1004  				queue: []*matcher.OrderRevealed{badLotsizeOrder},
  1005  			},
  1006  			doesMatch:        false,
  1007  			wantMatches:      nil,
  1008  			wantNumPassed:    0,
  1009  			wantNumFailed:    1,
  1010  			wantDoneOK:       0,
  1011  			wantNumPartial:   0,
  1012  			wantNumBooked:    0,
  1013  			wantNumUnbooked:  0,
  1014  			wantNumNomatched: 0,
  1015  		},
  1016  		{
  1017  			name: "market sell against empty book",
  1018  			args: args{
  1019  				book:  book.New(LotSize, 0),
  1020  				queue: []*matcher.OrderRevealed{takers[0]},
  1021  			},
  1022  			doesMatch:        false,
  1023  			wantMatches:      nil,
  1024  			wantNumPassed:    0,
  1025  			wantNumFailed:    1,
  1026  			wantDoneOK:       0,
  1027  			wantNumPartial:   0,
  1028  			wantNumBooked:    0,
  1029  			wantNumUnbooked:  0,
  1030  			wantNumNomatched: 1,
  1031  		},
  1032  	}
  1033  	for _, tt := range tests {
  1034  		t.Run(tt.name, func(t *testing.T) {
  1035  			// Reset Filled amounts of all pre-defined orders before each test.
  1036  			resetTakers()
  1037  			resetMakers()
  1038  
  1039  			//fmt.Printf("%v\n", takers)
  1040  
  1041  			// Ignore the seed since it is tested in the matcher unit tests.
  1042  			_, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, stats := me.Match(tt.args.book, tt.args.queue)
  1043  			matchMade := len(matches) > 0 && matches[0] != nil
  1044  			if tt.doesMatch != matchMade {
  1045  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
  1046  			}
  1047  			if len(matches) != len(tt.wantMatches) {
  1048  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
  1049  			}
  1050  			for i := range matches {
  1051  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
  1052  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
  1053  				}
  1054  			}
  1055  			if len(passed) != tt.wantNumPassed {
  1056  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
  1057  			}
  1058  			if len(failed) != tt.wantNumFailed {
  1059  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
  1060  			}
  1061  			if len(doneOK) != tt.wantDoneOK {
  1062  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
  1063  			}
  1064  			if len(partial) != tt.wantNumPartial {
  1065  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
  1066  			}
  1067  			if len(booked) != tt.wantNumBooked {
  1068  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked)
  1069  			}
  1070  			if len(unbooked) != tt.wantNumUnbooked {
  1071  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
  1072  			}
  1073  
  1074  			if len(nomatched) != tt.wantNumNomatched {
  1075  				t.Errorf("number nomatched %d, expected %d", len(nomatched), tt.wantNumNomatched)
  1076  			}
  1077  			if tt.matchStats != nil {
  1078  				compareMatchStats(t, tt.matchStats, stats)
  1079  			}
  1080  		})
  1081  	}
  1082  }
  1083  
  1084  // marketBuyQuoteAmt gives the exact amount in the quote asset require to
  1085  // purchase lots worth of the base asset given the current sell order book.
  1086  func marketBuyQuoteAmt(lots uint64) uint64 {
  1087  	var amt uint64
  1088  	var i int
  1089  	nSell := len(bookSellOrders)
  1090  	for lots > 0 && i < nSell {
  1091  		sellOrder := bookSellOrders[nSell-1-i]
  1092  		orderLots := sellOrder.Quantity / LotSize
  1093  		if orderLots > lots {
  1094  			orderLots = lots
  1095  		}
  1096  		lots -= orderLots
  1097  
  1098  		amt += matcher.BaseToQuote(sellOrder.Rate, orderLots*LotSize)
  1099  		i++
  1100  	}
  1101  	return amt
  1102  }
  1103  
  1104  // quoteAmt computes the required amount of the quote asset required to purchase
  1105  // the specified number of lots given the current order book and required amount
  1106  // buffering in the single lot case.
  1107  func quoteAmt(lots uint64) uint64 {
  1108  	amt := marketBuyQuoteAmt(lots)
  1109  	if lots == 1 {
  1110  		amt *= 3
  1111  		amt /= 2
  1112  	}
  1113  	return amt
  1114  }
  1115  
  1116  func TestMatch_marketBuysOnly(t *testing.T) {
  1117  	// Setup the match package's logger.
  1118  	startLogger()
  1119  
  1120  	// New matching engine.
  1121  	me := matcher.New()
  1122  
  1123  	rnd.Seed(0)
  1124  
  1125  	nSell := len(bookSellOrders)
  1126  	//nBuy := len(bookBuyOrders)
  1127  
  1128  	// takers is heterogenous w.r.t. type
  1129  	takers := []*matcher.OrderRevealed{
  1130  		newMarketBuy(quoteAmt(1), 0),  // buy, 1 lot
  1131  		newMarketBuy(quoteAmt(2), 0),  // buy, 2 lot
  1132  		newMarketBuy(quoteAmt(3), 0),  // buy, 3 lot
  1133  		newMarketBuy(quoteAmt(99), 0), // buy, up to 99 lots, computed exactly for the book
  1134  	}
  1135  
  1136  	resetTakers := func() {
  1137  		for _, o := range takers {
  1138  			switch ot := o.Order.(type) {
  1139  			case *order.MarketOrder:
  1140  				ot.FillAmt = 0
  1141  			case *order.LimitOrder:
  1142  				ot.FillAmt = 0
  1143  			}
  1144  		}
  1145  	}
  1146  
  1147  	bookSellOrdersReverse := make([]*order.LimitOrder, len(bookSellOrders))
  1148  	for i := range bookSellOrders {
  1149  		bookSellOrdersReverse[len(bookSellOrders)-1-i] = bookSellOrders[i]
  1150  	}
  1151  
  1152  	type args struct {
  1153  		book  *book.Book
  1154  		queue []*matcher.OrderRevealed
  1155  	}
  1156  	tests := []struct {
  1157  		name            string
  1158  		args            args
  1159  		doesMatch       bool
  1160  		wantMatches     []*order.MatchSet
  1161  		remaining       []uint64
  1162  		wantNumPassed   int
  1163  		wantNumFailed   int
  1164  		wantDoneOK      int
  1165  		wantNumPartial  int
  1166  		wantNumBooked   int
  1167  		wantNumUnbooked int
  1168  	}{
  1169  		{
  1170  			name: "market buy, 1 maker match",
  1171  			args: args{
  1172  				book:  newBook(t),
  1173  				queue: []*matcher.OrderRevealed{takers[0]},
  1174  			},
  1175  			doesMatch: true,
  1176  			wantMatches: []*order.MatchSet{
  1177  				newMatchSet(takers[0].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
  1178  			},
  1179  			remaining:       []uint64{quoteAmt(1) - marketBuyQuoteAmt(1)},
  1180  			wantNumPassed:   1,
  1181  			wantNumFailed:   0,
  1182  			wantDoneOK:      1,
  1183  			wantNumPartial:  0,
  1184  			wantNumBooked:   0,
  1185  			wantNumUnbooked: 1,
  1186  		},
  1187  		{
  1188  			name: "market buy, 2 maker match",
  1189  			args: args{
  1190  				book:  newBook(t),
  1191  				queue: []*matcher.OrderRevealed{takers[1]},
  1192  			},
  1193  			doesMatch: true,
  1194  			wantMatches: []*order.MatchSet{
  1195  				newMatchSet(takers[1].Order,
  1196  					[]*order.LimitOrder{bookSellOrders[nSell-1], bookSellOrders[nSell-2]},
  1197  					1*LotSize),
  1198  			},
  1199  			remaining:       []uint64{0},
  1200  			wantNumPassed:   1,
  1201  			wantNumFailed:   0,
  1202  			wantDoneOK:      1,
  1203  			wantNumPartial:  0,
  1204  			wantNumBooked:   0,
  1205  			wantNumUnbooked: 1,
  1206  		},
  1207  		{
  1208  			name: "market buy, 3 maker match",
  1209  			args: args{
  1210  				book:  newBook(t),
  1211  				queue: []*matcher.OrderRevealed{takers[2]},
  1212  			},
  1213  			doesMatch: true,
  1214  			wantMatches: []*order.MatchSet{
  1215  				newMatchSet(takers[2].Order,
  1216  					[]*order.LimitOrder{bookSellOrders[nSell-1], bookSellOrders[nSell-2]}),
  1217  			},
  1218  			remaining:       []uint64{0},
  1219  			wantNumPassed:   1,
  1220  			wantNumFailed:   0,
  1221  			wantDoneOK:      1,
  1222  			wantNumPartial:  0,
  1223  			wantNumBooked:   0,
  1224  			wantNumUnbooked: 2,
  1225  		},
  1226  		{
  1227  			name: "market buy, 99 maker match",
  1228  			args: args{
  1229  				book:  newBook(t),
  1230  				queue: []*matcher.OrderRevealed{takers[3]},
  1231  			},
  1232  			doesMatch: true,
  1233  			wantMatches: []*order.MatchSet{
  1234  				newMatchSet(takers[3].Order, bookSellOrdersReverse),
  1235  			},
  1236  			remaining:       []uint64{0},
  1237  			wantNumPassed:   1,
  1238  			wantNumFailed:   0,
  1239  			wantDoneOK:      1,
  1240  			wantNumPartial:  0,
  1241  			wantNumBooked:   0,
  1242  			wantNumUnbooked: 11,
  1243  		},
  1244  	}
  1245  	for _, tt := range tests {
  1246  		t.Run(tt.name, func(t *testing.T) {
  1247  			// Reset Filled amounts of all pre-defined orders before each test.
  1248  			resetTakers()
  1249  			resetMakers()
  1250  
  1251  			// Ignore the seed since it is tested in the matcher unit tests.
  1252  			_, matches, passed, failed, doneOK, partial, booked, _, unbooked, _, _ := me.Match(tt.args.book, tt.args.queue)
  1253  			matchMade := len(matches) > 0 && matches[0] != nil
  1254  			if tt.doesMatch != matchMade {
  1255  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
  1256  			}
  1257  			if len(matches) != len(tt.wantMatches) {
  1258  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
  1259  			}
  1260  			for i := range matches {
  1261  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
  1262  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
  1263  				}
  1264  				if matches[i].Taker.Trade().Remaining() != tt.remaining[i] {
  1265  					t.Errorf("Incorrect taker order amount remaining. Expected %d, got %d",
  1266  						tt.remaining[i], matches[i].Taker.Trade().Remaining())
  1267  				}
  1268  			}
  1269  			if len(passed) != tt.wantNumPassed {
  1270  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
  1271  			}
  1272  			if len(failed) != tt.wantNumFailed {
  1273  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
  1274  			}
  1275  			if len(doneOK) != tt.wantDoneOK {
  1276  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
  1277  			}
  1278  			if len(partial) != tt.wantNumPartial {
  1279  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
  1280  			}
  1281  			if len(booked) != tt.wantNumBooked {
  1282  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumBooked)
  1283  			}
  1284  			if len(unbooked) != tt.wantNumUnbooked {
  1285  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
  1286  			}
  1287  		})
  1288  	}
  1289  }
  1290  
  1291  func TestMatchWithBook_everything_multipleQueued(t *testing.T) {
  1292  	// Setup the match package's logger.
  1293  	startLogger()
  1294  
  1295  	// New matching engine.
  1296  	me := matcher.New()
  1297  
  1298  	rnd.Seed(12)
  1299  
  1300  	nSell := len(bookSellOrders)
  1301  	nBuy := len(bookBuyOrders)
  1302  	cancelTime := time.Unix(1566497655, 0)
  1303  
  1304  	// epochQueue is heterogenous w.r.t. type
  1305  	epochQueue := []*matcher.OrderRevealed{
  1306  		// buys
  1307  		newLimit(false, 4550000, 1, order.ImmediateTiF, 0), // 0: buy, 1 lot, immediate
  1308  		newLimit(false, 4550000, 2, order.StandingTiF, 0),  // 1: buy, 2 lot, standing
  1309  		newLimit(false, 4550000, 2, order.ImmediateTiF, 0), // 2: buy, 2 lot, immediate, unmatched
  1310  		newLimit(false, 4100000, 1, order.ImmediateTiF, 0), // 3: buy, 1 lot, immediate, unmatched
  1311  		// sells
  1312  		newLimit(true, 4540000, 1, order.ImmediateTiF, 0), // 4: sell, 1 lot, immediate
  1313  		newLimit(true, 4800000, 4, order.StandingTiF, 0),  // 5: sell, 4 lot, immediate, unmatched
  1314  		newLimit(true, 4300000, 4, order.ImmediateTiF, 0), // 6: sell, 4 lot, immediate
  1315  		newLimit(true, 4800000, 40, order.StandingTiF, 1), // 7: sell, 40 lot, standing, unfilled insert
  1316  		// market
  1317  		newMarketSell(2, 0),          // 8
  1318  		newMarketSell(4, 0),          // 9
  1319  		newMarketBuy(quoteAmt(1), 0), // 10
  1320  		newMarketBuy(quoteAmt(2), 0), // 11
  1321  		// cancel
  1322  		newCancel(bookSellOrders[6].ID(), cancelTime),       // 12
  1323  		newCancel(bookBuyOrders[8].ID(), cancelTime),        // 13
  1324  		newCancel(bookBuyOrders[nBuy-1].ID(), cancelTime),   // 14
  1325  		newCancel(bookSellOrders[nSell-1].ID(), cancelTime), // 15
  1326  	}
  1327  	// cancel some the epoch queue orders too
  1328  	epochQueue = append(epochQueue, newCancel(epochQueue[7].Order.ID(), cancelTime)) // 16 cancels 7 (miss)
  1329  	epochQueue = append(epochQueue, newCancel(epochQueue[5].Order.ID(), cancelTime)) // 17 cancels 5 (miss)
  1330  
  1331  	epochQueueInit := make([]*matcher.OrderRevealed, len(epochQueue))
  1332  	copy(epochQueueInit, epochQueue)
  1333  
  1334  	// var shuf []int
  1335  	// matcher.ShuffleQueue(epochQueue)
  1336  	// for i := range epochQueue {
  1337  	// 	for j := range epochQueueInit {
  1338  	// 		if epochQueue[i].Order.ID() == epochQueueInit[j].Order.ID() {
  1339  	// 			shuf = append(shuf, j)
  1340  	// 			t.Logf("%d: %p", j, epochQueueInit[j].Order)
  1341  	// 			continue
  1342  	// 		}
  1343  	// 	}
  1344  	// }
  1345  	// t.Logf("%#v", shuf)
  1346  
  1347  	// Apply the shuffling to determine matching order that will be used.
  1348  	// matcher.ShuffleQueue(epochQueue)
  1349  	// for i := range epochQueue {
  1350  	// 	t.Logf("%d: %p, %p", i, epochQueueInit[i].Order, epochQueue[i].Order)
  1351  	// }
  1352  	// Shuffles to [6, 13, 0, 14, 11, 10, 7, 1, 12, 5, 17, 4, 16, 2, 9, 8, 15, 3]
  1353  
  1354  	expectedNumMatches := 11
  1355  	expectedPassed := []int{5, 0, 8, 10, 13, 6, 17, 9, 7, 16, 12, 1, 4, 11}
  1356  	expectedFailed := []int{15, 3, 2, 14}
  1357  	expectedDoneOK := []int{0, 8, 10, 13, 6, 17, 9, 16, 12, 4, 11}
  1358  	expectedPartial := []int{}
  1359  	expectedBooked := []int{5, 7, 1} // all StandingTiF
  1360  	expectedNumUnbooked := 8
  1361  	expectedNumNomatched := 6
  1362  
  1363  	// order book from bookBuyOrders and bookSellOrders
  1364  	b := newBook(t)
  1365  
  1366  	resetQueue := func() {
  1367  		for _, o := range epochQueue {
  1368  			switch ot := o.Order.(type) {
  1369  			case *order.MarketOrder:
  1370  				ot.FillAmt = 0
  1371  			case *order.LimitOrder:
  1372  				ot.FillAmt = 0
  1373  			}
  1374  		}
  1375  	}
  1376  
  1377  	// Reset Filled amounts of all pre-defined orders before each test.
  1378  	resetQueue()
  1379  	resetMakers()
  1380  
  1381  	// Ignore the seed since it is tested in the matcher unit tests.
  1382  	_, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, _, _ := me.Match(b, epochQueue)
  1383  	//t.Log("Matches:", matches)
  1384  	// s := "Passed: "
  1385  	// for _, o := range passed {
  1386  	// 	s += fmt.Sprintf("%p ", o.Order)
  1387  	// }
  1388  	// t.Log(s)
  1389  	// s = "Failed: "
  1390  	// for _, o := range failed {
  1391  	// 	s += fmt.Sprintf("%p ", o.Order)
  1392  	// }
  1393  	// t.Log(s)
  1394  	// s = "DoneOK: "
  1395  	// for _, o := range doneOK {
  1396  	// 	s += fmt.Sprintf("%p ", o.Order)
  1397  	// }
  1398  	// t.Log(s)
  1399  	// s = "Partial: "
  1400  	// for _, o := range partial {
  1401  	// 	s += fmt.Sprintf("%p ", o.Order)
  1402  	// }
  1403  	// t.Log(s)
  1404  	// s = "Booked: "
  1405  	// for _, o := range booked {
  1406  	// 	s += fmt.Sprintf("%p ", o.Order)
  1407  	// }
  1408  	// t.Log(s)
  1409  	// s := "Nomatched: "
  1410  	// for _, o := range nomatched {
  1411  	// 	s += fmt.Sprintf("%p ", o.Order)
  1412  	// }
  1413  	// t.Log(s)
  1414  	// s = "Unbooked: "
  1415  	// for _, o := range unbooked {
  1416  	// 	s += fmt.Sprintf("%p ", o)
  1417  	// }
  1418  	// t.Log(s)
  1419  
  1420  	// for i := range matches {
  1421  	// 	t.Logf("Match %d: %p, [%p, ...]", i, matches[i].Taker, matches[i].Makers[0])
  1422  	// }
  1423  
  1424  	// PASSED orders
  1425  
  1426  	for i, qi := range expectedPassed {
  1427  		if oi := orderInSlice(epochQueueInit[qi], passed); oi != i {
  1428  			t.Errorf("Order not at expected location in passed slice. Got %d, expected %d",
  1429  				oi, i)
  1430  		}
  1431  	}
  1432  
  1433  	for i, qi := range expectedFailed {
  1434  		if oi := orderInSlice(epochQueueInit[qi], failed); oi != i {
  1435  			t.Errorf("Order not at expected location in failed slice. Got %d, expected %d",
  1436  				oi, i)
  1437  		}
  1438  	}
  1439  
  1440  	for i, qi := range expectedDoneOK {
  1441  		if oi := orderInSlice(epochQueueInit[qi], doneOK); oi != i {
  1442  			t.Errorf("Order not at expected location in doneOK slice. Got %d, expected %d",
  1443  				oi, i)
  1444  		}
  1445  	}
  1446  
  1447  	for i, qi := range expectedPartial {
  1448  		if oi := orderInSlice(epochQueueInit[qi], partial); oi != i {
  1449  			t.Errorf("Order not at expected location in partial slice. Got %d, expected %d",
  1450  				oi, i)
  1451  		}
  1452  	}
  1453  
  1454  	for i, qi := range expectedBooked {
  1455  		if oi := orderInSlice(epochQueueInit[qi], booked); oi != i {
  1456  			t.Errorf("Order not at expected location in booked slice. Got %d, expected %d",
  1457  				oi, i)
  1458  		}
  1459  	}
  1460  
  1461  	if len(unbooked) != expectedNumUnbooked {
  1462  		t.Errorf("Incorrect number of unbooked orders. Got %d, expected %d", len(unbooked), expectedNumUnbooked)
  1463  	}
  1464  
  1465  	if len(nomatched) != expectedNumNomatched {
  1466  		t.Errorf("Incorrect number of nomatched orders. Got %d, expected %d", len(nomatched), expectedNumNomatched)
  1467  	}
  1468  
  1469  	if len(matches) != expectedNumMatches {
  1470  		t.Errorf("Incorrect number of matches. Got %d, expected %d", len(matches), expectedNumMatches)
  1471  	}
  1472  
  1473  	// Spot check a couple of matches.
  1474  
  1475  	// match 4 (epoch order 6) cancels a book order
  1476  	if matches[4].Taker.ID() != epochQueueInit[6].Order.ID() {
  1477  		t.Errorf("Taker order ID expected %v, got %v",
  1478  			epochQueueInit[6].Order.UID(), matches[4].Taker.UID())
  1479  	}
  1480  	if matches[9].Makers[0].ID() != epochQueueInit[1].Order.ID() {
  1481  		t.Errorf("9th match maker was expected to be %v, got %v",
  1482  			epochQueueInit[1].Order.UID(), matches[9].Makers[0].ID())
  1483  	}
  1484  }
  1485  
  1486  func compareMatchStats(t *testing.T, sWant, sHave *matcher.MatchCycleStats) {
  1487  	t.Helper()
  1488  	if sWant.BookBuys != sHave.BookBuys {
  1489  		t.Errorf("wrong BookBuys. wanted %d, got %d", sWant.BookBuys, sHave.BookBuys)
  1490  	}
  1491  	// if sWant.BookBuys5 != sHave.BookBuys5 {
  1492  	// 	t.Errorf("wrong BookBuys5. wanted %d, got %d", sWant.BookBuys5, sHave.BookBuys5)
  1493  	// }
  1494  	// if sWant.BookBuys25 != sHave.BookBuys25 {
  1495  	// 	t.Errorf("wrong BookBuys25. wanted %d, got %d", sWant.BookBuys25, sHave.BookBuys25)
  1496  	// }
  1497  	if sWant.BookSells != sHave.BookSells {
  1498  		t.Errorf("wrong BookSells. wanted %d, got %d", sWant.BookSells, sHave.BookSells)
  1499  	}
  1500  	// if sWant.BookSells5 != sHave.BookSells5 {
  1501  	// 	t.Errorf("wrong BookSells5. wanted %d, got %d", sWant.BookSells5, sHave.BookSells5)
  1502  	// }
  1503  	// if sWant.BookSells25 != sHave.BookSells25 {
  1504  	// 	t.Errorf("wrong BookSells25. wanted %d, got %d", sWant.BookSells25, sHave.BookSells25)
  1505  	// }
  1506  	if sWant.HighRate != sHave.HighRate {
  1507  		t.Errorf("wrong HighRate. wanted %d, got %d", sWant.HighRate, sHave.HighRate)
  1508  	}
  1509  	if sWant.LowRate != sHave.LowRate {
  1510  		t.Errorf("wrong LowRate. wanted %d, got %d", sWant.LowRate, sHave.LowRate)
  1511  	}
  1512  	if sWant.StartRate != sHave.StartRate {
  1513  		t.Errorf("wrong StartRate. wanted %d, got %d", sWant.StartRate, sHave.StartRate)
  1514  	}
  1515  	if sWant.EndRate != sHave.EndRate {
  1516  		t.Errorf("wrong EndRate. wanted %d, got %d", sWant.EndRate, sHave.EndRate)
  1517  	}
  1518  }