Engine QA (#367)

* Improved error message when no config is set on startup

* Change inccorect error wording

* bump Bitfinex websocket orderbook return length to max

* temporary fix of incorrect orderbook updates, limit to bid and ask len of 100, will be extended later if needed

* Fixed issue in binance websocket that appended 0 volume bid/ask items

* Fix panic when unmarshalling an empty pair from config

* Add get pair asset method for exchange base
Fix Bitmex orderbook stream
Unbuffer Bitmex orderbook stream

* force syncer to update ticker instead of fetch, which allows a stream

* Fix websocket last price for coinbasepro

* fix websocket ticker for coinut

* Fix websocket orderbook stream Huobi

* increase orderbook depth REST for Huobi

* Fix websocket support and ensure data integrity

* Fix time parsing issue after error checks

* check error, only process enabled currency pairs, signal websocket data processing

* expanded websocket functionality for okgroup

* Add logic to not process zero length slice for orderbooks

* fix websocket ticker only updating enabled and individual book updates

* ZB fixes to order submission/retrieval/cancellation w/ general fixes

* Quiet unnecessary warning

* updated config entry values for REST and websocket (initial hack until I come up with a better solution for asset types)

* Ch GetName function to field access modifyer & rm useless code

* Add in error I missed

* Nits addressed

* some more fixes

* Turned kraken default websocket to true and some small changes

* fixes linter issues

* Ensured okgroup books and sent update through to datahandler. Zb update as well.

* Add test case to get asset type from pair

* Add test for pairs unmarshal

* Add testing and addressed nits

* FIX linter issue

* Addressed Gees nits

* Thanks glorious spotter

* more nitorinos

* Addres even more nits

* Add stringerino 4000

* Fix for panic cause by sort slice out of range, also nits addressed

* fix linter issues

* Changed from function to field access

* Changed from function to field access

* fix for orderbook update panic, removes quick fix - caused by sync item fetching through same protocol

* Add new test and update random generator

* pass in invalid string to future ob fetching, due to futures contract expire and a http 400 error is returned
This commit is contained in:
Ryan O'Hara-Reid
2019-11-04 15:34:30 +11:00
committed by Adrian Gallagher
parent e2c349424f
commit 22ff33cd54
53 changed files with 1813 additions and 1074 deletions

View File

@@ -25,27 +25,28 @@ func (w *WebsocketOrderbookLocal) Setup(obBufferLimit int, bufferEnabled, sortBu
// Volume == 0; deletion at price target
// Price target not found; append of price target
// Price target found; amend volume of price target
func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpdate) error {
if (orderbookUpdate.Bids == nil && orderbookUpdate.Asks == nil) ||
(len(orderbookUpdate.Bids) == 0 && len(orderbookUpdate.Asks) == 0) {
return fmt.Errorf("%v cannot have bids and ask targets both nil", w.exchangeName)
func (w *WebsocketOrderbookLocal) Update(u *WebsocketOrderbookUpdate) error {
if (u.Bids == nil && u.Asks == nil) || (len(u.Bids) == 0 && len(u.Asks) == 0) {
return fmt.Errorf("%v cannot have bids and ask targets both nil",
w.exchangeName)
}
w.m.Lock()
defer w.m.Unlock()
obLookup, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]
obLookup, ok := w.ob[u.Pair][u.Asset]
if !ok {
return fmt.Errorf("ob.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
w.exchangeName,
orderbookUpdate.CurrencyPair.String(),
orderbookUpdate.AssetType)
u.Pair,
u.Asset)
}
if w.bufferEnabled {
overBufferLimit := w.processBufferUpdate(obLookup, orderbookUpdate)
overBufferLimit := w.processBufferUpdate(obLookup, u)
if !overBufferLimit {
return nil
}
} else {
w.processObUpdate(obLookup, orderbookUpdate)
w.processObUpdate(obLookup, u)
}
err := obLookup.Process()
if err != nil {
@@ -53,23 +54,23 @@ func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpda
}
if w.bufferEnabled {
// Reset the buffer
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = nil
w.buffer[u.Pair][u.Asset] = nil
}
return nil
}
func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) bool {
func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) bool {
if w.buffer == nil {
w.buffer = make(map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate)
}
if w.buffer[orderbookUpdate.CurrencyPair] == nil {
w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]*WebsocketOrderbookUpdate)
if w.buffer[u.Pair] == nil {
w.buffer[u.Pair] = make(map[asset.Item][]*WebsocketOrderbookUpdate)
}
bufferLookup := w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]
bufferLookup := w.buffer[u.Pair][u.Asset]
if len(bufferLookup) <= w.obBufferLimit {
bufferLookup = append(bufferLookup, orderbookUpdate)
bufferLookup = append(bufferLookup, u)
if len(bufferLookup) < w.obBufferLimit {
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup
w.buffer[u.Pair][u.Asset] = bufferLookup
return false
}
}
@@ -88,61 +89,59 @@ func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbo
for i := 0; i < len(bufferLookup); i++ {
w.processObUpdate(o, bufferLookup[i])
}
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup
w.buffer[u.Pair][u.Asset] = bufferLookup
return true
}
func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) {
func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
if w.updateEntriesByID {
w.updateByIDAndAction(o, orderbookUpdate)
w.updateByIDAndAction(o, u)
} else {
w.updateAsksByPrice(o, orderbookUpdate)
w.updateBidsByPrice(o, orderbookUpdate)
w.updateAsksByPrice(o, u)
w.updateBidsByPrice(o, u)
}
}
func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) {
for j := 0; j < len(base.Asks); j++ {
found := false
for k := 0; k < len(o.Asks); k++ {
if o.Asks[k].Price == base.Asks[j].Price {
found = true
if base.Asks[j].Amount == 0 {
o.Asks = append(o.Asks[:k],
o.Asks[k+1:]...)
break
func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
updates:
for j := range u.Asks {
for k := range o.Asks {
if o.Asks[k].Price == u.Asks[j].Price {
if u.Asks[j].Amount <= 0 {
o.Asks = append(o.Asks[:k], o.Asks[k+1:]...)
continue updates
}
o.Asks[k].Amount = base.Asks[j].Amount
break
o.Asks[k].Amount = u.Asks[j].Amount
continue updates
}
}
if !found {
o.Asks = append(o.Asks, base.Asks[j])
if u.Asks[j].Amount == 0 {
continue
}
o.Asks = append(o.Asks, u.Asks[j])
}
sort.Slice(o.Asks, func(i, j int) bool {
return o.Asks[i].Price < o.Asks[j].Price
})
}
func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) {
for j := 0; j < len(base.Bids); j++ {
found := false
for k := 0; k < len(o.Bids); k++ {
if o.Bids[k].Price == base.Bids[j].Price {
found = true
if base.Bids[j].Amount == 0 {
o.Bids = append(o.Bids[:k],
o.Bids[k+1:]...)
break
func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
updates:
for j := range u.Bids {
for k := range o.Bids {
if o.Bids[k].Price == u.Bids[j].Price {
if u.Bids[j].Amount <= 0 {
o.Bids = append(o.Bids[:k], o.Bids[k+1:]...)
continue updates
}
o.Bids[k].Amount = base.Bids[j].Amount
break
o.Bids[k].Amount = u.Bids[j].Amount
continue updates
}
}
if !found {
o.Bids = append(o.Bids, base.Bids[j])
if u.Bids[j].Amount == 0 {
continue
}
o.Bids = append(o.Bids, u.Bids[j])
}
sort.Slice(o.Bids, func(i, j int) bool {
return o.Bids[i].Price > o.Bids[j].Price
@@ -151,49 +150,52 @@ func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *Web
// updateByIDAndAction will receive an action to execute against the orderbook
// it will then match by IDs instead of price to perform the action
func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) {
switch orderbookUpdate.Action {
func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, u *WebsocketOrderbookUpdate) {
switch u.Action {
case "update":
for _, target := range orderbookUpdate.Bids {
for i := range o.Bids {
if o.Bids[i].ID == target.ID {
o.Bids[i].Amount = target.Amount
for x := range u.Bids {
for y := range o.Bids {
if o.Bids[y].ID == u.Bids[x].ID {
o.Bids[y].Amount = u.Bids[x].Amount
break
}
}
}
for _, target := range orderbookUpdate.Asks {
for i := range o.Asks {
if o.Asks[i].ID == target.ID {
o.Asks[i].Amount = target.Amount
for x := range u.Asks {
for y := range o.Asks {
if o.Asks[y].ID == u.Asks[x].ID {
o.Asks[y].Amount = u.Asks[x].Amount
break
}
}
}
case "delete":
for _, target := range orderbookUpdate.Bids {
for i := 0; i < len(o.Bids); i++ {
if o.Bids[i].ID == target.ID {
o.Bids = append(o.Bids[:i],
o.Bids[i+1:]...)
i--
for x := range u.Bids {
for y := 0; y < len(o.Bids); y++ {
if o.Bids[y].ID == u.Bids[x].ID {
o.Bids = append(o.Bids[:y], o.Bids[y+1:]...)
break
}
}
}
for _, target := range orderbookUpdate.Asks {
for i := 0; i < len(o.Asks); i++ {
if o.Asks[i].ID == target.ID {
o.Asks = append(o.Asks[:i],
o.Asks[i+1:]...)
i--
for x := range u.Asks {
for y := 0; y < len(o.Asks); y++ {
if o.Asks[y].ID == u.Asks[x].ID {
o.Asks = append(o.Asks[:y], o.Asks[y+1:]...)
break
}
}
}
case "insert":
o.Bids = append(o.Bids, orderbookUpdate.Bids...)
o.Asks = append(o.Asks, orderbookUpdate.Asks...)
o.Bids = append(o.Bids, u.Bids...)
sort.Slice(o.Bids, func(i, j int) bool {
return o.Bids[i].Price > o.Bids[j].Price
})
o.Asks = append(o.Asks, u.Asks...)
sort.Slice(o.Asks, func(i, j int) bool {
return o.Asks[i].Price < o.Asks[j].Price
})
}
}
@@ -225,22 +227,17 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) err
if w.ob[newOrderbook.Pair] == nil {
w.ob[newOrderbook.Pair] = make(map[asset.Item]*orderbook.Base)
}
fullObLookup := w.ob[newOrderbook.Pair][newOrderbook.AssetType]
if fullObLookup != nil &&
(len(fullObLookup.Asks) > 0 ||
len(fullObLookup.Bids) > 0) {
fullObLookup = newOrderbook
return newOrderbook.Process()
}
w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook
return newOrderbook.Process()
}
// GetOrderbook use sparingly. Modifying anything here will ruin hash calculation and cause problems
func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, assetType asset.Item) *orderbook.Base {
// GetOrderbook use sparingly. Modifying anything here will ruin hash
// calculation and cause problems
func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, a asset.Item) *orderbook.Base {
w.m.Lock()
defer w.m.Unlock()
return w.ob[p][assetType]
return w.ob[p][a]
}
// FlushCache flushes w.ob data to be garbage collected and refreshed when a

View File

@@ -20,14 +20,15 @@ var itemArray = [][]orderbook.Item{
{{Price: 5000, Amount: 1, ID: 5}},
}
var cp = currency.NewPairFromString("BTCUSD")
const (
exchangeName = "exchangeTest"
)
func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, bids []orderbook.Item, err error) {
func createSnapshot() (obl *WebsocketOrderbookLocal, asks, bids []orderbook.Item, err error) {
var snapShot1 orderbook.Base
snapShot1.ExchangeName = exchangeName
curr = currency.NewPairFromString("BTCUSD")
asks = []orderbook.Item{
{Price: 4000, Amount: 1, ID: 6},
}
@@ -37,7 +38,7 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, b
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
snapShot1.Pair = cp
obl = &WebsocketOrderbookLocal{exchangeName: exchangeName}
err = obl.LoadSnapshot(&snapShot1)
return
@@ -52,7 +53,7 @@ func bidAskGenerator() []orderbook.Item {
price = 1
}
response = append(response, orderbook.Item{
Amount: float64(rand.Intn(1)),
Amount: float64(rand.Intn(10)),
Price: price,
ID: int64(i),
})
@@ -61,7 +62,7 @@ func bidAskGenerator() []orderbook.Item {
}
func BenchmarkUpdateBidsByPrice(b *testing.B) {
ob, curr, _, _, err := createSnapshot()
ob, _, _, err := createSnapshot()
if err != nil {
b.Error(err)
}
@@ -69,18 +70,18 @@ func BenchmarkUpdateBidsByPrice(b *testing.B) {
for i := 0; i < b.N; i++ {
bidAsks := bidAskGenerator()
update := &WebsocketOrderbookUpdate{
Bids: bidAsks,
Asks: bidAsks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bidAsks,
Asks: bidAsks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
}
ob.updateBidsByPrice(ob.ob[curr][asset.Spot], update)
ob.updateBidsByPrice(ob.ob[cp][asset.Spot], update)
}
}
func BenchmarkUpdateAsksByPrice(b *testing.B) {
ob, curr, _, _, err := createSnapshot()
ob, _, _, err := createSnapshot()
if err != nil {
b.Error(err)
}
@@ -88,25 +89,24 @@ func BenchmarkUpdateAsksByPrice(b *testing.B) {
for i := 0; i < b.N; i++ {
bidAsks := bidAskGenerator()
update := &WebsocketOrderbookUpdate{
Bids: bidAsks,
Asks: bidAsks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bidAsks,
Asks: bidAsks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
}
ob.updateAsksByPrice(ob.ob[curr][asset.Spot], update)
ob.updateAsksByPrice(ob.ob[cp][asset.Spot], update)
}
}
// BenchmarkBufferPerformance demonstrates buffer more performant than multi
// process calls
func BenchmarkBufferPerformance(b *testing.B) {
obl, curr, asks, bids, err := createSnapshot()
obl, asks, bids, err := createSnapshot()
if err != nil {
b.Fatal(err)
}
obl.bufferEnabled = true
cp := currency.NewPairFromString("BTCUSD")
// This is to ensure we do not send in zero orderbook info to our main book
// in orderbook.go, orderbooks should not be zero even after an update.
dummyItem := orderbook.Item{
@@ -116,11 +116,11 @@ func BenchmarkBufferPerformance(b *testing.B) {
}
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
update := &WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -136,13 +136,12 @@ func BenchmarkBufferPerformance(b *testing.B) {
// BenchmarkBufferSortingPerformance benchmark
func BenchmarkBufferSortingPerformance(b *testing.B) {
obl, curr, asks, bids, err := createSnapshot()
obl, asks, bids, err := createSnapshot()
if err != nil {
b.Fatal(err)
}
obl.bufferEnabled = true
obl.sortBuffer = true
cp := currency.NewPairFromString("BTCUSD")
// This is to ensure we do not send in zero orderbook info to our main book
// in orderbook.go, orderbooks should not be zero even after an update.
dummyItem := orderbook.Item{
@@ -152,11 +151,11 @@ func BenchmarkBufferSortingPerformance(b *testing.B) {
}
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
update := &WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -172,14 +171,13 @@ func BenchmarkBufferSortingPerformance(b *testing.B) {
// BenchmarkBufferSortingPerformance benchmark
func BenchmarkBufferSortingByIDPerformance(b *testing.B) {
obl, curr, asks, bids, err := createSnapshot()
obl, asks, bids, err := createSnapshot()
if err != nil {
b.Fatal(err)
}
obl.bufferEnabled = true
obl.sortBuffer = true
obl.sortBufferByUpdateIDs = true
cp := currency.NewPairFromString("BTCUSD")
// This is to ensure we do not send in zero orderbook info to our main book
// in orderbook.go, orderbooks should not be zero even after an update.
dummyItem := orderbook.Item{
@@ -189,11 +187,11 @@ func BenchmarkBufferSortingByIDPerformance(b *testing.B) {
}
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
update := &WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -210,11 +208,10 @@ func BenchmarkBufferSortingByIDPerformance(b *testing.B) {
// BenchmarkNoBufferPerformance demonstrates orderbook process less performant
// than buffer
func BenchmarkNoBufferPerformance(b *testing.B) {
obl, curr, asks, bids, err := createSnapshot()
obl, asks, bids, err := createSnapshot()
if err != nil {
b.Fatal(err)
}
cp := currency.NewPairFromString("BTCUSD")
// This is to ensure we do not send in zero orderbook info to our main book
// in orderbook.go, orderbooks should not be zero even after an update.
dummyItem := orderbook.Item{
@@ -224,11 +221,11 @@ func BenchmarkNoBufferPerformance(b *testing.B) {
}
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
update := &WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -243,41 +240,41 @@ func BenchmarkNoBufferPerformance(b *testing.B) {
}
func TestUpdates(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Error(err)
}
obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{
Bids: itemArray[5],
Asks: itemArray[5],
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{
Bids: itemArray[5],
Asks: itemArray[5],
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
})
if err != nil {
t.Error(err)
}
obl.updateAsksByPrice(obl.ob[curr][asset.Spot], &WebsocketOrderbookUpdate{
Bids: itemArray[0],
Asks: itemArray[0],
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{
Bids: itemArray[0],
Asks: itemArray[0],
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
})
if err != nil {
t.Error(err)
}
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
if len(obl.ob[cp][asset.Spot].Asks) != 3 {
t.Error("Did not update")
}
}
// TestHittingTheBuffer logic test
func TestHittingTheBuffer(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
@@ -287,30 +284,30 @@ func TestHittingTheBuffer(t *testing.T) {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
t.Log(obl.ob[curr][asset.Spot])
if len(obl.ob[cp][asset.Spot].Asks) != 3 {
t.Log(obl.ob[cp][asset.Spot])
t.Errorf("expected 3 entries, received: %v",
len(obl.ob[curr][asset.Spot].Asks))
len(obl.ob[cp][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
if len(obl.ob[cp][asset.Spot].Bids) != 3 {
t.Errorf("expected 3 entries, received: %v",
len(obl.ob[curr][asset.Spot].Bids))
len(obl.ob[cp][asset.Spot].Bids))
}
}
// TestInsertWithIDs logic test
func TestInsertWithIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
@@ -321,30 +318,30 @@ func TestInsertWithIDs(t *testing.T) {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Action: "insert",
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
Action: "insert",
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 6 {
if len(obl.ob[cp][asset.Spot].Asks) != 6 {
t.Errorf("expected 6 entries, received: %v",
len(obl.ob[curr][asset.Spot].Asks))
len(obl.ob[cp][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 6 {
if len(obl.ob[cp][asset.Spot].Bids) != 6 {
t.Errorf("expected 6 entries, received: %v",
len(obl.ob[curr][asset.Spot].Bids))
len(obl.ob[cp][asset.Spot].Bids))
}
}
// TestSortIDs logic test
func TestSortIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
@@ -356,34 +353,33 @@ func TestSortIDs(t *testing.T) {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateID: int64(i),
AssetType: asset.Spot,
Bids: bids,
Asks: asks,
Pair: cp,
UpdateID: int64(i),
Asset: asset.Spot,
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
if len(obl.ob[cp][asset.Spot].Asks) != 3 {
t.Errorf("expected 3 entries, received: %v",
len(obl.ob[curr][asset.Spot].Asks))
len(obl.ob[cp][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
if len(obl.ob[cp][asset.Spot].Bids) != 3 {
t.Errorf("expected 3 entries, received: %v",
len(obl.ob[curr][asset.Spot].Bids))
len(obl.ob[cp][asset.Spot].Bids))
}
}
// TestDeleteWithIDs logic test
func TestDeleteWithIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
cp := currency.NewPairFromString("BTCUSD")
// This is to ensure we do not send in zero orderbook info to our main book
// in orderbook.go, orderbooks should not be zero even after an update.
dummyItem := orderbook.Item{
@@ -392,35 +388,41 @@ func TestDeleteWithIDs(t *testing.T) {
ID: 1337,
}
obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem)
obl.ob[cp][asset.Spot].Asks = append(obl.ob[cp][asset.Spot].Asks,
itemArray[2][0])
obl.ob[cp][asset.Spot].Asks = append(obl.ob[cp][asset.Spot].Asks,
itemArray[1][0])
obl.updateEntriesByID = true
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Action: "delete",
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
Action: "delete",
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 0 {
if len(obl.ob[cp][asset.Spot].Asks) != 0 {
t.Errorf("expected 0 entries, received: %v",
len(obl.ob[curr][asset.Spot].Asks))
len(obl.ob[cp][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 1 {
if len(obl.ob[cp][asset.Spot].Bids) != 1 {
t.Errorf("expected 1 entries, received: %v",
len(obl.ob[curr][asset.Spot].Bids))
len(obl.ob[cp][asset.Spot].Bids))
}
}
// TestUpdateWithIDs logic test
func TestUpdateWithIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
@@ -429,35 +431,38 @@ func TestUpdateWithIDs(t *testing.T) {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Action: "update",
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
Action: "update",
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 1 {
t.Log(obl.ob[curr][asset.Spot])
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
if len(obl.ob[cp][asset.Spot].Asks) != 1 {
t.Log(obl.ob[cp][asset.Spot])
t.Errorf("expected 1 entries, received: %v",
len(obl.ob[cp][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 1 {
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
if len(obl.ob[cp][asset.Spot].Bids) != 1 {
t.Errorf("expected 1 entries, received: %v",
len(obl.ob[cp][asset.Spot].Bids))
}
}
// TestOutOfOrderIDs logic test
func TestOutOfOrderIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
outOFOrderIDs := []int64{2, 1, 5, 3, 4, 6, 7}
if itemArray[0][0].Price != 1000 {
t.Errorf("expected sorted price to be 3000, received: %v", itemArray[1][0].Price)
t.Errorf("expected sorted price to be 3000, received: %v",
itemArray[1][0].Price)
}
obl.bufferEnabled = true
obl.sortBuffer = true
@@ -465,18 +470,19 @@ func TestOutOfOrderIDs(t *testing.T) {
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Asks: asks,
CurrencyPair: curr,
UpdateID: outOFOrderIDs[i],
AssetType: asset.Spot,
Asks: asks,
Pair: cp,
UpdateID: outOFOrderIDs[i],
Asset: asset.Spot,
})
if err != nil {
t.Fatal(err)
}
}
// Index 1 since index 0 is price 7000
if obl.ob[curr][asset.Spot].Asks[1].Price != 2000 {
t.Errorf("expected sorted price to be 3000, received: %v", obl.ob[curr][asset.Spot].Asks[1].Price)
if obl.ob[cp][asset.Spot].Asks[1].Price != 2000 {
t.Errorf("expected sorted price to be 3000, received: %v",
obl.ob[cp][asset.Spot].Asks[1].Price)
}
}
@@ -484,7 +490,6 @@ func TestOutOfOrderIDs(t *testing.T) {
func TestRunUpdateWithoutSnapshot(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
curr := currency.NewPairFromString("BTCUSD")
asks := []orderbook.Item{
{Price: 4000, Amount: 1, ID: 8},
}
@@ -495,14 +500,14 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) {
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
snapShot1.Pair = cp
obl.exchangeName = exchangeName
err := obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: bids,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
})
if err == nil {
t.Fatal("expected an error running update with no snapshot loaded")
@@ -516,18 +521,17 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) {
func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
curr := currency.NewPairFromString("BTCUSD")
snapShot1.Asks = []orderbook.Item{}
snapShot1.Bids = []orderbook.Item{}
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
snapShot1.Pair = cp
obl.exchangeName = exchangeName
err := obl.Update(&WebsocketOrderbookUpdate{
Bids: snapShot1.Asks,
Asks: snapShot1.Bids,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Bids: snapShot1.Asks,
Asks: snapShot1.Bids,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
})
if err == nil {
t.Fatal("expected an error running update with no snapshot loaded")
@@ -542,11 +546,10 @@ func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
func TestRunSnapshotWithNoData(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
curr := currency.NewPairFromString("BTCUSD")
snapShot1.Asks = []orderbook.Item{}
snapShot1.Bids = []orderbook.Item{}
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
snapShot1.Pair = cp
snapShot1.ExchangeName = "test"
obl.exchangeName = "test"
err := obl.LoadSnapshot(&snapShot1)
@@ -563,7 +566,6 @@ func TestLoadSnapshot(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
snapShot1.ExchangeName = "SnapshotWithOverride"
curr := currency.NewPairFromString("BTCUSD")
asks := []orderbook.Item{
{Price: 4000, Amount: 1, ID: 8},
}
@@ -573,7 +575,7 @@ func TestLoadSnapshot(t *testing.T) {
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
snapShot1.Pair = cp
err := obl.LoadSnapshot(&snapShot1)
if err != nil {
t.Error(err)
@@ -582,15 +584,15 @@ func TestLoadSnapshot(t *testing.T) {
// TestFlushCache logic test
func TestFlushCache(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
if obl.ob[curr][asset.Spot] == nil {
if obl.ob[cp][asset.Spot] == nil {
t.Error("expected ob to have ask entries")
}
obl.FlushCache()
if obl.ob[curr][asset.Spot] != nil {
if obl.ob[cp][asset.Spot] != nil {
t.Error("expected ob be flushed")
}
@@ -632,7 +634,7 @@ func TestInsertingSnapShots(t *testing.T) {
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = currency.NewPairFromString("BTCUSD")
snapShot1.Pair = cp
err := obl.LoadSnapshot(&snapShot1)
if err != nil {
t.Fatal(err)
@@ -714,23 +716,29 @@ func TestInsertingSnapShots(t *testing.T) {
t.Fatal(err)
}
if obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0] != snapShot1.Asks[0] {
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot1.Asks[0], obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0])
t.Errorf("loaded data mismatch. Expected %v, received %v",
snapShot1.Asks[0],
obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0])
}
if obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0] != snapShot2.Asks[0] {
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot2.Asks[0], obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0])
t.Errorf("loaded data mismatch. Expected %v, received %v",
snapShot2.Asks[0],
obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0])
}
if obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0] != snapShot3.Asks[0] {
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot3.Asks[0], obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0])
t.Errorf("loaded data mismatch. Expected %v, received %v",
snapShot3.Asks[0],
obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0])
}
}
func TestGetOrderbook(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
obl, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
ob := obl.GetOrderbook(curr, asset.Spot)
if obl.ob[curr][asset.Spot] != ob {
ob := obl.GetOrderbook(cp, asset.Spot)
if obl.ob[cp][asset.Spot] != ob {
t.Error("Failed to get orderbook")
}
}
@@ -738,7 +746,35 @@ func TestGetOrderbook(t *testing.T) {
func TestSetup(t *testing.T) {
w := WebsocketOrderbookLocal{}
w.Setup(1, true, true, true, true, "hi")
if w.obBufferLimit != 1 || !w.bufferEnabled || !w.sortBuffer || !w.sortBufferByUpdateIDs || !w.updateEntriesByID || w.exchangeName != "hi" {
if w.obBufferLimit != 1 ||
!w.bufferEnabled ||
!w.sortBuffer ||
!w.sortBufferByUpdateIDs ||
!w.updateEntriesByID ||
w.exchangeName != "hi" {
t.Errorf("Setup incorrectly loaded %s", w.exchangeName)
}
}
func TestEnsureMultipleUpdatesViaPrice(t *testing.T) {
obl, _, _, err := createSnapshot()
if err != nil {
t.Error(err)
}
asks := bidAskGenerator()
obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{
Bids: asks,
Asks: asks,
Pair: cp,
UpdateTime: time.Now(),
Asset: asset.Spot,
})
if err != nil {
t.Error(err)
}
if len(obl.ob[cp][asset.Spot].Asks) <= 3 {
t.Errorf("Insufficient updates")
}
}

View File

@@ -25,11 +25,11 @@ type WebsocketOrderbookLocal struct {
// WebsocketOrderbookUpdate stores orderbook updates and dictates what features to use when processing
type WebsocketOrderbookUpdate struct {
UpdateID int64 // Used when no time is provided
UpdateTime time.Time
AssetType asset.Item
Action string // Used in conjunction with UpdateEntriesByID
Bids []orderbook.Item
Asks []orderbook.Item
CurrencyPair currency.Pair
UpdateID int64 // Used when no time is provided
UpdateTime time.Time
Asset asset.Item
Action string // Used in conjunction with UpdateEntriesByID
Bids []orderbook.Item
Asks []orderbook.Item
Pair currency.Pair
}