mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* initial concept of a nice validation tester for exchanges * adds some datahandler design * expand testing * more tests and fixes * minor end of day fix for bithumb * fixes implementation issues * more test coverage and improvements, but not sure if i should continue * fix more wrapper implementations * adds error type, more fixes * changes signature, fixes implementations * fixes more wrapper implementations * one more bit * more cleanup * WOW things work? * lintle 1/1337 * mini bump * fixes all linting * neaten * GetOrderInfo+ asset pair fixes+improvements * adds new websocket test * expand ws testing * fix bug, expand tests, improve implementation * code coverage of a lot of new codes * fixes everything * reverts accidental changes * minor fixes from reviewing code * removes Bitfinex cancelBatchOrder implementation * fixes dumb baby typo for babies * mini nit fixes * so many nits to address * addresses all the nits * Titlecase * switcheroo * removes websocket testing for now * fix appveyor, minor test fix * fixes typo, re-kindles killed kode * skip binance wrapper tests when running CI * expired context, huobi okx fixes * kodespull * fix ordering * time fix because why not * fix exmo, others * hopefully this fixes all of my life's problems * last thing today * huobi, more like hypotrophy * golangci-lint, more like mypooroldknee-splint * fix huobi times by removing them * should fix okx currency issues * blocks the application * adds last little contingency for pairs * addresses most nits and new problems * lovely fixed before seeing why okx sucks * fixes issues with okx websocket * the classic receieieivaier * lintle * adds test and fixes existing tests * expands error handling messages during setup * fixes dumb okx bugs introduced * quick fix for lint and exmo * fixes nixes * fix exmo deposit issue * lint * fixes issue with extra asset runs missing * fix surprise race * all the lint and merge fixes * fixes surprise bugs in OKx * fixes issues with times and chains * fixing all the merge stuff * merge fix * rm logs and a panic potential * lovely lint lament * an easy demonstration of scenario, but not of initial purpose * put it in the bin * Revert "put it in the bin" This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd. * re-add after immediate error popup * fix mini poor test design * okx okay * merge fixes * fixes issues discovered in lovely test * I FORGOT TO COMMIT THIS * nit fixaroonaboo * forgoetten test fix * revert old okx asset intrument work * fixes * revert problems I didnt understand. update bybit * fix merge bugs * test cleanup * further improvements * reshuffle and lint * rm redundant CI_TEST by rm the CI_TEST field that is redundant * path fix * move to its own section, dont run on 32 bit + appveyor * lint * fix lbank * address nits * let it rip * fix failing test time range * niteroo boogaloo * mod tidy, use common.SimpleTimeFormat
2148 lines
51 KiB
Go
2148 lines
51 KiB
Go
package orderbook
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var ask = Items{
|
|
Item{Price: 1337, Amount: 1},
|
|
Item{Price: 1338, Amount: 1},
|
|
Item{Price: 1339, Amount: 1},
|
|
Item{Price: 1340, Amount: 1},
|
|
Item{Price: 1341, Amount: 1},
|
|
Item{Price: 1342, Amount: 1},
|
|
Item{Price: 1343, Amount: 1},
|
|
Item{Price: 1344, Amount: 1},
|
|
Item{Price: 1345, Amount: 1},
|
|
Item{Price: 1346, Amount: 1},
|
|
Item{Price: 1347, Amount: 1},
|
|
Item{Price: 1348, Amount: 1},
|
|
Item{Price: 1349, Amount: 1},
|
|
Item{Price: 1350, Amount: 1},
|
|
Item{Price: 1351, Amount: 1},
|
|
Item{Price: 1352, Amount: 1},
|
|
Item{Price: 1353, Amount: 1},
|
|
Item{Price: 1354, Amount: 1},
|
|
Item{Price: 1355, Amount: 1},
|
|
Item{Price: 1356, Amount: 1},
|
|
}
|
|
|
|
var bid = Items{
|
|
Item{Price: 1336, Amount: 1},
|
|
Item{Price: 1335, Amount: 1},
|
|
Item{Price: 1334, Amount: 1},
|
|
Item{Price: 1333, Amount: 1},
|
|
Item{Price: 1332, Amount: 1},
|
|
Item{Price: 1331, Amount: 1},
|
|
Item{Price: 1330, Amount: 1},
|
|
Item{Price: 1329, Amount: 1},
|
|
Item{Price: 1328, Amount: 1},
|
|
Item{Price: 1327, Amount: 1},
|
|
Item{Price: 1326, Amount: 1},
|
|
Item{Price: 1325, Amount: 1},
|
|
Item{Price: 1324, Amount: 1},
|
|
Item{Price: 1323, Amount: 1},
|
|
Item{Price: 1322, Amount: 1},
|
|
Item{Price: 1321, Amount: 1},
|
|
Item{Price: 1320, Amount: 1},
|
|
Item{Price: 1319, Amount: 1},
|
|
Item{Price: 1318, Amount: 1},
|
|
Item{Price: 1317, Amount: 1},
|
|
}
|
|
|
|
// Display displays depth content for tests
|
|
func (ll *linkedList) display() {
|
|
for tip := ll.head; tip != nil; tip = tip.Next {
|
|
fmt.Printf("NODE: %+v %p \n", tip, tip)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
func TestLoad(t *testing.T) {
|
|
list := asks{}
|
|
Check(t, list, 0, 0, 0)
|
|
|
|
stack := newStack()
|
|
list.load(Items{
|
|
{Price: 1, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
{Price: 7, Amount: 1},
|
|
{Price: 9, Amount: 1},
|
|
{Price: 11, Amount: 1},
|
|
}, stack)
|
|
|
|
if stack.getCount() != 0 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
Check(t, list, 6, 36, 6)
|
|
|
|
list.load(Items{
|
|
{Price: 1, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
}, stack)
|
|
|
|
if stack.getCount() != 3 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 3, stack.getCount())
|
|
}
|
|
|
|
Check(t, list, 3, 9, 3)
|
|
|
|
list.load(Items{
|
|
{Price: 1, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
{Price: 7, Amount: 1},
|
|
}, stack)
|
|
|
|
if stack.getCount() != 2 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 2, stack.getCount())
|
|
}
|
|
|
|
Check(t, list, 4, 16, 4)
|
|
|
|
// purge entire list
|
|
list.load(nil, stack)
|
|
|
|
if stack.getCount() != 6 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 6, stack.getCount())
|
|
}
|
|
|
|
Check(t, list, 0, 0, 0)
|
|
}
|
|
|
|
// 22222386 57.3 ns/op 0 B/op 0 allocs/op (old)
|
|
// 27906781 42.4 ns/op 0 B/op 0 allocs/op (new)
|
|
func BenchmarkLoad(b *testing.B) {
|
|
ll := linkedList{}
|
|
s := newStack()
|
|
for i := 0; i < b.N; i++ {
|
|
ll.load(ask, s)
|
|
}
|
|
}
|
|
|
|
func TestUpdateInsertByPrice(t *testing.T) {
|
|
a := asks{}
|
|
stack := newStack()
|
|
asksSnapshot := Items{
|
|
{Price: 1, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
{Price: 7, Amount: 1},
|
|
{Price: 9, Amount: 1},
|
|
{Price: 11, Amount: 1},
|
|
}
|
|
a.load(asksSnapshot, stack)
|
|
|
|
// Update one instance with matching price
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 1, Amount: 2},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, a, 7, 37, 6)
|
|
|
|
if stack.getCount() != 0 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
// Insert at head
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 0.5, Amount: 2},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, a, 9, 38, 7)
|
|
|
|
if stack.getCount() != 0 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
// Insert at tail
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 12, Amount: 2},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, a, 11, 62, 8)
|
|
|
|
if stack.getCount() != 0 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
// Insert between price and up to and beyond max allowable depth level
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 11.5, Amount: 2},
|
|
{Price: 10.5, Amount: 2},
|
|
{Price: 13, Amount: 2},
|
|
}, stack, 10, getNow())
|
|
|
|
Check(t, a, 15, 106, 10)
|
|
|
|
if stack.getCount() != 1 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 1, stack.getCount())
|
|
}
|
|
|
|
// delete at tail
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 12, Amount: 0},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, a, 13, 82, 9)
|
|
|
|
if stack.getCount() != 2 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 2, stack.getCount())
|
|
}
|
|
|
|
// delete at mid
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 7, Amount: 0},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, a, 12, 75, 8)
|
|
|
|
if stack.getCount() != 3 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 3, stack.getCount())
|
|
}
|
|
|
|
// delete at head
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 0.5, Amount: 0},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, a, 10, 74, 7)
|
|
|
|
if stack.getCount() != 4 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 4, stack.getCount())
|
|
}
|
|
|
|
// purge if liquidity plunges to zero
|
|
a.load(nil, stack)
|
|
|
|
// rebuild everything again
|
|
a.updateInsertByPrice(Items{
|
|
{Price: 1, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
{Price: 7, Amount: 1},
|
|
{Price: 9, Amount: 1},
|
|
{Price: 11, Amount: 1},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, a, 6, 36, 6)
|
|
|
|
if stack.getCount() != 5 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 4, stack.getCount())
|
|
}
|
|
|
|
b := bids{}
|
|
bidsSnapshot := Items{
|
|
{Price: 11, Amount: 1},
|
|
{Price: 9, Amount: 1},
|
|
{Price: 7, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 1, Amount: 1},
|
|
}
|
|
b.load(bidsSnapshot, stack)
|
|
|
|
// Update one instance with matching price
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 11, Amount: 2},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, b, 7, 47, 6)
|
|
|
|
if stack.getCount() != 0 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
// Insert at head
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 12, Amount: 2},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, b, 9, 71, 7)
|
|
|
|
if stack.getCount() != 0 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
// Insert at tail
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 0.5, Amount: 2},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, b, 11, 72, 8)
|
|
|
|
if stack.getCount() != 0 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
// Insert between price and up to and beyond max allowable depth level
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 11.5, Amount: 2},
|
|
{Price: 10.5, Amount: 2},
|
|
{Price: 13, Amount: 2},
|
|
}, stack, 10, getNow())
|
|
|
|
Check(t, b, 15, 141, 10)
|
|
|
|
if stack.getCount() != 1 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount())
|
|
}
|
|
|
|
// Insert between price and up to and beyond max allowable depth level
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 1, Amount: 0},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, b, 14, 140, 9)
|
|
|
|
if stack.getCount() != 2 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 2, stack.getCount())
|
|
}
|
|
|
|
// delete at mid
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 10.5, Amount: 0},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, b, 12, 119, 8)
|
|
|
|
if stack.getCount() != 3 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 3, stack.getCount())
|
|
}
|
|
|
|
// delete at head
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 13, Amount: 0},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, b, 10, 93, 7)
|
|
|
|
if stack.getCount() != 4 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 4, stack.getCount())
|
|
}
|
|
|
|
// purge if liquidity plunges to zero
|
|
b.load(nil, stack)
|
|
|
|
// rebuild everything again
|
|
b.updateInsertByPrice(Items{
|
|
{Price: 1, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
{Price: 7, Amount: 1},
|
|
{Price: 9, Amount: 1},
|
|
{Price: 11, Amount: 1},
|
|
}, stack, 0, getNow())
|
|
|
|
Check(t, b, 6, 36, 6)
|
|
|
|
if stack.getCount() != 5 {
|
|
t.Fatalf("incorrect stack count expected: %v received: %v", 4, stack.getCount())
|
|
}
|
|
}
|
|
|
|
func TestCleanup(t *testing.T) {
|
|
a := asks{}
|
|
stack := newStack()
|
|
asksSnapshot := Items{
|
|
{Price: 1, Amount: 1},
|
|
{Price: 3, Amount: 1},
|
|
{Price: 5, Amount: 1},
|
|
{Price: 7, Amount: 1},
|
|
{Price: 9, Amount: 1},
|
|
{Price: 11, Amount: 1},
|
|
}
|
|
a.load(asksSnapshot, stack)
|
|
|
|
a.cleanup(6, stack)
|
|
Check(t, a, 6, 36, 6)
|
|
a.cleanup(5, stack)
|
|
Check(t, a, 5, 25, 5)
|
|
a.cleanup(1, stack)
|
|
Check(t, a, 1, 1, 1)
|
|
a.cleanup(10, stack)
|
|
Check(t, a, 1, 1, 1)
|
|
a.cleanup(0, stack) // will purge, underlying checks are done elseware to prevent this
|
|
Check(t, a, 0, 0, 0)
|
|
}
|
|
|
|
// 46154023 24.0 ns/op 0 B/op 0 allocs/op (old)
|
|
// 134830672 9.83 ns/op 0 B/op 0 allocs/op (new)
|
|
func BenchmarkUpdateInsertByPrice_Amend(b *testing.B) {
|
|
a := asks{}
|
|
stack := newStack()
|
|
|
|
a.load(ask, stack)
|
|
|
|
updates := Items{
|
|
{
|
|
Price: 1337, // Amend
|
|
Amount: 2,
|
|
},
|
|
{
|
|
Price: 1337, // Amend
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
a.updateInsertByPrice(updates, stack, 0, getNow())
|
|
}
|
|
}
|
|
|
|
// 49763002 24.9 ns/op 0 B/op 0 allocs/op
|
|
func BenchmarkUpdateInsertByPrice_Insert_Delete(b *testing.B) {
|
|
a := asks{}
|
|
stack := newStack()
|
|
|
|
a.load(ask, stack)
|
|
|
|
updates := Items{
|
|
{
|
|
Price: 1337.5, // Insert
|
|
Amount: 2,
|
|
},
|
|
{
|
|
Price: 1337.5, // Delete
|
|
Amount: 0,
|
|
},
|
|
}
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
a.updateInsertByPrice(updates, stack, 0, getNow())
|
|
}
|
|
}
|
|
|
|
func TestUpdateByID(t *testing.T) {
|
|
a := asks{}
|
|
s := newStack()
|
|
asksSnapshot := Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
}
|
|
a.load(asksSnapshot, s)
|
|
|
|
err := a.updateByID(Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 6, 36, 6)
|
|
|
|
err = a.updateByID(Items{
|
|
{Price: 11, Amount: 1, ID: 1337},
|
|
})
|
|
if !errors.Is(err, errIDCannotBeMatched) {
|
|
t.Fatalf("expecting %s but received %v", errIDCannotBeMatched, err)
|
|
}
|
|
|
|
err = a.updateByID(Items{ // Simulate Bitmex updating
|
|
{Price: 0, Amount: 1337, ID: 3},
|
|
})
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("expecting %v but received %v", nil, err)
|
|
}
|
|
|
|
if a.retrieve()[1].Price == 0 {
|
|
t.Fatal("price should not be replaced with zero")
|
|
}
|
|
|
|
if a.retrieve()[1].Amount != 1337 {
|
|
t.Fatal("unexpected value for update")
|
|
}
|
|
}
|
|
|
|
// 46043871 25.9 ns/op 0 B/op 0 allocs/op
|
|
func BenchmarkUpdateByID(b *testing.B) {
|
|
asks := linkedList{}
|
|
s := newStack()
|
|
asksSnapshot := Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
}
|
|
asks.load(asksSnapshot, s)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
err := asks.updateByID(Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
})
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDeleteByID(t *testing.T) {
|
|
a := asks{}
|
|
s := newStack()
|
|
asksSnapshot := Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
}
|
|
a.load(asksSnapshot, s)
|
|
|
|
// Delete at head
|
|
err := a.deleteByID(Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
}, s, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 5, 35, 5)
|
|
|
|
// Delete at tail
|
|
err = a.deleteByID(Items{
|
|
{Price: 1, Amount: 1, ID: 11},
|
|
}, s, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 4, 24, 4)
|
|
|
|
// Delete in middle
|
|
err = a.deleteByID(Items{
|
|
{Price: 1, Amount: 1, ID: 5},
|
|
}, s, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 3, 19, 3)
|
|
|
|
// Intentional error
|
|
err = a.deleteByID(Items{
|
|
{Price: 11, Amount: 1, ID: 1337},
|
|
}, s, false)
|
|
if !errors.Is(err, errIDCannotBeMatched) {
|
|
t.Fatalf("expecting %s but received %v", errIDCannotBeMatched, err)
|
|
}
|
|
|
|
// Error bypass
|
|
err = a.deleteByID(Items{
|
|
{Price: 11, Amount: 1, ID: 1337},
|
|
}, s, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestUpdateInsertByIDAsk(t *testing.T) {
|
|
a := asks{}
|
|
s := newStack()
|
|
asksSnapshot := Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
}
|
|
a.load(asksSnapshot, s)
|
|
|
|
// Update one instance with matching ID
|
|
err := a.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 7, 37, 6)
|
|
|
|
// Reset
|
|
a.load(asksSnapshot, s)
|
|
|
|
// Update all instances with matching ID in order
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 5, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 12, 72, 6)
|
|
|
|
// Update all instances with matching ID in backwards
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 5, Amount: 2, ID: 5},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 12, 72, 6)
|
|
|
|
// Update all instances with matching ID all over the ship
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 5, Amount: 2, ID: 5},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 12, 72, 6)
|
|
|
|
// Update all instances move one before ID in middle
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 2, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 12, 66, 6)
|
|
|
|
// Update all instances move one before ID at head
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: .5, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 12, 63, 6)
|
|
|
|
// Reset
|
|
a.load(asksSnapshot, s)
|
|
|
|
// Update all instances move one after ID
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 8, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 12, 78, 6)
|
|
|
|
// Reset
|
|
a.load(asksSnapshot, s)
|
|
|
|
// Update all instances move one after ID to tail
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 12, 86, 6)
|
|
|
|
// Update all instances then pop new instance
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
{Price: 10, Amount: 2, ID: 10},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 14, 106, 7)
|
|
|
|
// Reset
|
|
a.load(asksSnapshot, s)
|
|
|
|
// Update all instances pop at head
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 0.5, Amount: 2, ID: 0},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 14, 87, 7)
|
|
|
|
// bookmark head and move to mid
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 7.5, Amount: 2, ID: 0},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 14, 101, 7)
|
|
|
|
// bookmark head and move to tail
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 12.5, Amount: 2, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 14, 124, 7)
|
|
|
|
// move tail location to head
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 2.5, Amount: 2, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 14, 104, 7)
|
|
|
|
// move tail location to mid
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 8, Amount: 2, ID: 5},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 14, 96, 7)
|
|
|
|
// insert at tail dont match
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 30, Amount: 2, ID: 1234},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 16, 156, 8)
|
|
|
|
// insert between last and 2nd last
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 12, Amount: 2, ID: 12345},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 18, 180, 9)
|
|
|
|
// readjust at end
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 29, Amount: 3, ID: 1234},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 19, 207, 9)
|
|
|
|
// readjust further and decrease price past tail
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 31, Amount: 3, ID: 1234},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 19, 213, 9)
|
|
|
|
// purge
|
|
a.load(nil, s)
|
|
|
|
// insert with no liquidity and jumbled
|
|
err = a.updateInsertByID(Items{
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 0.5, Amount: 2, ID: 0},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 14, 87, 7)
|
|
}
|
|
|
|
func TestUpdateInsertByIDBids(t *testing.T) {
|
|
b := bids{}
|
|
s := newStack()
|
|
bidsSnapshot := Items{
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
}
|
|
b.load(bidsSnapshot, s)
|
|
|
|
// Update one instance with matching ID
|
|
err := b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 7, 37, 6)
|
|
|
|
// Reset
|
|
b.load(bidsSnapshot, s)
|
|
|
|
// Update all instances with matching ID in order
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 5, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 12, 72, 6)
|
|
|
|
// Update all instances with matching ID in backwards
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 5, Amount: 2, ID: 5},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 12, 72, 6)
|
|
|
|
// Update all instances with matching ID all over the ship
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 5, Amount: 2, ID: 5},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 12, 72, 6)
|
|
|
|
// Update all instances move one before ID in middle
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 2, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 12, 66, 6)
|
|
|
|
// Update all instances move one before ID at head
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: .5, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 12, 63, 6)
|
|
|
|
// Reset
|
|
b.load(bidsSnapshot, s)
|
|
|
|
// Update all instances move one after ID
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 8, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 12, 78, 6)
|
|
|
|
// Reset
|
|
b.load(bidsSnapshot, s)
|
|
|
|
// Update all instances move one after ID to tail
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 12, 86, 6)
|
|
|
|
// Update all instances then pop new instance
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
{Price: 10, Amount: 2, ID: 10},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 14, 106, 7)
|
|
|
|
// Reset
|
|
b.load(bidsSnapshot, s)
|
|
|
|
// Update all instances pop at tail
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 0.5, Amount: 2, ID: 0},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 14, 87, 7)
|
|
|
|
// bookmark head and move to mid
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 9.5, Amount: 2, ID: 5},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 14, 82, 7)
|
|
|
|
// bookmark head and move to tail
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 0.25, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 14, 60.5, 7)
|
|
|
|
// move tail location to head
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 10, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 14, 80, 7)
|
|
|
|
// move tail location to mid
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 7.5, Amount: 2, ID: 0},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 14, 94, 7)
|
|
|
|
// insert at head dont match
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 30, Amount: 2, ID: 1234},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 16, 154, 8)
|
|
|
|
// insert between last and 2nd last
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1.5, Amount: 2, ID: 12345},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
Check(t, b, 18, 157, 9)
|
|
|
|
// readjust at end
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 1, Amount: 3, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
Check(t, b, 19, 158, 9)
|
|
|
|
// readjust further and decrease price past tail
|
|
err = b.updateInsertByID(Items{
|
|
{Price: .9, Amount: 3, ID: 1},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
Check(t, b, 19, 157.7, 9)
|
|
|
|
// purge
|
|
b.load(nil, s)
|
|
|
|
// insert with no liquidity and jumbled
|
|
err = b.updateInsertByID(Items{
|
|
{Price: 0.5, Amount: 2, ID: 0},
|
|
{Price: 1, Amount: 2, ID: 1},
|
|
{Price: 3, Amount: 2, ID: 3},
|
|
{Price: 12, Amount: 2, ID: 5},
|
|
{Price: 7, Amount: 2, ID: 7},
|
|
{Price: 9, Amount: 2, ID: 9},
|
|
{Price: 11, Amount: 2, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 14, 87, 7)
|
|
}
|
|
|
|
func TestInsertUpdatesBid(t *testing.T) {
|
|
b := bids{}
|
|
s := newStack()
|
|
bidsSnapshot := Items{
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
}
|
|
b.load(bidsSnapshot, s)
|
|
|
|
err := b.insertUpdates(Items{
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
}, s)
|
|
if !errors.Is(err, errCollisionDetected) {
|
|
t.Fatalf("expected error %s but received %v", errCollisionDetected, err)
|
|
}
|
|
|
|
Check(t, b, 6, 36, 6)
|
|
|
|
// Insert at head
|
|
err = b.insertUpdates(Items{
|
|
{Price: 12, Amount: 1, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 7, 48, 7)
|
|
|
|
// Insert at tail
|
|
err = b.insertUpdates(Items{
|
|
{Price: 0.5, Amount: 1, ID: 12},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 8, 48.5, 8)
|
|
|
|
// Insert at mid
|
|
err = b.insertUpdates(Items{
|
|
{Price: 5.5, Amount: 1, ID: 13},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 9, 54, 9)
|
|
|
|
// purge
|
|
b.load(nil, s)
|
|
|
|
// Add one at head
|
|
err = b.insertUpdates(Items{
|
|
{Price: 5.5, Amount: 1, ID: 13},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, b, 1, 5.5, 1)
|
|
}
|
|
|
|
func TestInsertUpdatesAsk(t *testing.T) {
|
|
a := asks{}
|
|
s := newStack()
|
|
askSnapshot := Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
}
|
|
a.load(askSnapshot, s)
|
|
|
|
err := a.insertUpdates(Items{
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
}, s)
|
|
if !errors.Is(err, errCollisionDetected) {
|
|
t.Fatalf("expected error %s but received %v", errCollisionDetected, err)
|
|
}
|
|
|
|
Check(t, a, 6, 36, 6)
|
|
|
|
// Insert at tail
|
|
err = a.insertUpdates(Items{
|
|
{Price: 12, Amount: 1, ID: 11},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 7, 48, 7)
|
|
|
|
// Insert at head
|
|
err = a.insertUpdates(Items{
|
|
{Price: 0.5, Amount: 1, ID: 12},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 8, 48.5, 8)
|
|
|
|
// Insert at mid
|
|
err = a.insertUpdates(Items{
|
|
{Price: 5.5, Amount: 1, ID: 13},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 9, 54, 9)
|
|
|
|
// purge
|
|
a.load(nil, s)
|
|
|
|
// Add one at head
|
|
err = a.insertUpdates(Items{
|
|
{Price: 5.5, Amount: 1, ID: 13},
|
|
}, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
Check(t, a, 1, 5.5, 1)
|
|
}
|
|
|
|
// check checks depth values after an update has taken place
|
|
func Check(t *testing.T, depth interface{}, liquidity, value float64, nodeCount int) {
|
|
t.Helper()
|
|
b, isBid := depth.(bids)
|
|
a, isAsk := depth.(asks)
|
|
|
|
var ll linkedList
|
|
switch {
|
|
case isBid:
|
|
ll = b.linkedList
|
|
case isAsk:
|
|
ll = a.linkedList
|
|
default:
|
|
t.Fatal("value passed in is not of type bids or asks")
|
|
}
|
|
|
|
liquidityTotal, valueTotal := ll.amount()
|
|
|
|
if liquidityTotal != liquidity {
|
|
ll.display()
|
|
t.Fatalf("mismatched liquidity expecting %v but received %v",
|
|
liquidity,
|
|
liquidityTotal)
|
|
}
|
|
|
|
if valueTotal != value {
|
|
ll.display()
|
|
t.Fatalf("mismatched total value expecting %v but received %v",
|
|
value,
|
|
valueTotal)
|
|
}
|
|
|
|
if ll.length != nodeCount {
|
|
ll.display()
|
|
t.Fatalf("mismatched node count expecting %v but received %v",
|
|
nodeCount,
|
|
ll.length)
|
|
}
|
|
|
|
if ll.head == nil {
|
|
return
|
|
}
|
|
|
|
var tail *Node
|
|
var price float64
|
|
for tip := ll.head; ; tip = tip.Next {
|
|
switch {
|
|
case price == 0:
|
|
price = tip.Value.Price
|
|
case isBid && price < tip.Value.Price:
|
|
ll.display()
|
|
t.Fatal("Bid pricing out of order should be descending")
|
|
case isAsk && price > tip.Value.Price:
|
|
ll.display()
|
|
t.Fatal("Ask pricing out of order should be ascending")
|
|
default:
|
|
price = tip.Value.Price
|
|
}
|
|
|
|
if tip.Next == nil {
|
|
tail = tip
|
|
break
|
|
}
|
|
}
|
|
|
|
var liqReversed, valReversed float64
|
|
var nodeReversed int
|
|
for tip := tail; tip != nil; tip = tip.Prev {
|
|
liqReversed += tip.Value.Amount
|
|
valReversed += tip.Value.Amount * tip.Value.Price
|
|
nodeReversed++
|
|
}
|
|
|
|
if liquidity-liqReversed != 0 {
|
|
ll.display()
|
|
fmt.Println(liquidity, liqReversed)
|
|
t.Fatalf("mismatched liquidity when reversing direction expecting %v but received %v",
|
|
0,
|
|
liquidity-liqReversed)
|
|
}
|
|
|
|
if nodeCount-nodeReversed != 0 {
|
|
ll.display()
|
|
t.Fatalf("mismatched node count when reversing direction expecting %v but received %v",
|
|
0,
|
|
nodeCount-nodeReversed)
|
|
}
|
|
|
|
if value-valReversed != 0 {
|
|
ll.display()
|
|
fmt.Println(valReversed, value)
|
|
t.Fatalf("mismatched total book value when reversing direction expecting %v but received %v",
|
|
0,
|
|
value-valReversed)
|
|
}
|
|
}
|
|
|
|
func TestAmount(t *testing.T) {
|
|
a := asks{}
|
|
s := newStack()
|
|
askSnapshot := Items{
|
|
{Price: 1, Amount: 1, ID: 1},
|
|
{Price: 3, Amount: 1, ID: 3},
|
|
{Price: 5, Amount: 1, ID: 5},
|
|
{Price: 7, Amount: 1, ID: 7},
|
|
{Price: 9, Amount: 1, ID: 9},
|
|
{Price: 11, Amount: 1, ID: 11},
|
|
}
|
|
a.load(askSnapshot, s)
|
|
|
|
liquidity, value := a.amount()
|
|
if liquidity != 6 {
|
|
t.Fatalf("incorrect liquidity calculation expected 6 but received %f", liquidity)
|
|
}
|
|
|
|
if value != 36 {
|
|
t.Fatalf("incorrect value calculation expected 36 but received %f", value)
|
|
}
|
|
}
|
|
|
|
func TestShiftBookmark(t *testing.T) {
|
|
bookmarkedNode := &Node{
|
|
Value: Item{
|
|
ID: 1337,
|
|
Amount: 1,
|
|
Price: 2,
|
|
},
|
|
Next: nil,
|
|
Prev: nil,
|
|
shelved: time.Time{},
|
|
}
|
|
|
|
originalBookmarkPrev := &Node{
|
|
Value: Item{
|
|
ID: 1336,
|
|
},
|
|
Next: bookmarkedNode,
|
|
Prev: nil, // At head
|
|
shelved: time.Time{},
|
|
}
|
|
originalBookmarkNext := &Node{
|
|
Value: Item{
|
|
ID: 1338,
|
|
},
|
|
Next: nil, // This can be left nil in actuality this will be
|
|
// populated
|
|
Prev: bookmarkedNode,
|
|
shelved: time.Time{},
|
|
}
|
|
|
|
// associate previous and next nodes to bookmarked node
|
|
bookmarkedNode.Prev = originalBookmarkPrev
|
|
bookmarkedNode.Next = originalBookmarkNext
|
|
|
|
tip := &Node{
|
|
Value: Item{
|
|
ID: 69420,
|
|
},
|
|
Next: nil, // In this case tip will be at tail
|
|
Prev: nil,
|
|
shelved: time.Time{},
|
|
}
|
|
|
|
tipprev := &Node{
|
|
Value: Item{
|
|
ID: 69419,
|
|
},
|
|
Next: tip,
|
|
Prev: nil, // This can be left nil in actuality this will be
|
|
// populated
|
|
shelved: time.Time{},
|
|
}
|
|
|
|
// associate tips prev field with the correct prev node
|
|
tip.Prev = tipprev
|
|
|
|
if !shiftBookmark(tip, &bookmarkedNode, nil, Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
|
t.Fatal("There should be liquidity so we don't need to set tip to bookmark")
|
|
}
|
|
|
|
if bookmarkedNode.Value.Price != 9999 ||
|
|
bookmarkedNode.Value.Amount != 1336 ||
|
|
bookmarkedNode.Value.ID != 1337 {
|
|
t.Fatal("bookmarked details are not set correctly with shift")
|
|
}
|
|
|
|
if bookmarkedNode.Prev != tip {
|
|
t.Fatal("bookmarked prev memory address does not point to tip")
|
|
}
|
|
|
|
if bookmarkedNode.Next != nil {
|
|
t.Fatal("bookmarked next is at tail and should be nil")
|
|
}
|
|
|
|
if bookmarkedNode.Next != nil {
|
|
t.Fatal("bookmarked next is at tail and should be nil")
|
|
}
|
|
|
|
if originalBookmarkPrev.Next != originalBookmarkNext {
|
|
t.Fatal("original bookmarked prev node should be associated with original bookmarked next node")
|
|
}
|
|
|
|
if originalBookmarkNext.Prev != originalBookmarkPrev {
|
|
t.Fatal("original bookmarked next node should be associated with original bookmarked prev node")
|
|
}
|
|
|
|
var nilBookmark *Node
|
|
|
|
if shiftBookmark(tip, &nilBookmark, nil, Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
|
t.Fatal("there should not be a bookmarked node")
|
|
}
|
|
|
|
if tip != nilBookmark {
|
|
t.Fatal("nilBookmark not reassigned")
|
|
}
|
|
|
|
head := bookmarkedNode
|
|
bookmarkedNode.Prev = nil
|
|
bookmarkedNode.Next = originalBookmarkNext
|
|
tip.Next = nil
|
|
|
|
if !shiftBookmark(tip, &bookmarkedNode, &head, Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
|
t.Fatal("There should be liquidity so we don't need to set tip to bookmark")
|
|
}
|
|
|
|
if head != originalBookmarkNext {
|
|
t.Fatal("unexpected pointer variable")
|
|
}
|
|
}
|
|
|
|
func TestGetMovementByBaseAmount(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
Name string
|
|
BaseAmount float64
|
|
ReferencePrice float64
|
|
BidLiquidity Items
|
|
ExpectedNominal float64
|
|
ExpectedImpact float64
|
|
ExpectedCost float64
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "no amount",
|
|
ExpectedError: errBaseAmountInvalid,
|
|
},
|
|
{
|
|
Name: "no reference price",
|
|
BaseAmount: 1,
|
|
ExpectedError: errInvalidReferencePrice,
|
|
},
|
|
{
|
|
Name: "not enough liquidity to service quote amount",
|
|
BaseAmount: 1,
|
|
ReferencePrice: 1000,
|
|
ExpectedError: errNoLiquidity,
|
|
},
|
|
{
|
|
Name: "thrasher test",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
BaseAmount: 10,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 0.8999999999999999,
|
|
ExpectedImpact: 2,
|
|
ExpectedCost: 900,
|
|
},
|
|
{
|
|
Name: "consume first tranche",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
BaseAmount: 2,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 0,
|
|
ExpectedImpact: 1,
|
|
ExpectedCost: 0,
|
|
},
|
|
{
|
|
Name: "consume most of first tranche",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
BaseAmount: 1.5,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 0,
|
|
ExpectedImpact: 0,
|
|
ExpectedCost: 0,
|
|
},
|
|
{
|
|
Name: "consume full liquidity",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
BaseAmount: 12,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 1.0833333333333395,
|
|
ExpectedImpact: FullLiquidityExhaustedPercentage,
|
|
ExpectedCost: 1300,
|
|
},
|
|
}
|
|
|
|
for x := range cases {
|
|
tt := cases[x]
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
depth := NewDepth(id)
|
|
depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true)
|
|
movement, err := depth.bids.getMovementByBase(tt.BaseAmount, tt.ReferencePrice, false)
|
|
if !errors.Is(err, tt.ExpectedError) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError)
|
|
}
|
|
|
|
if movement == nil {
|
|
return
|
|
}
|
|
if movement.NominalPercentage != tt.ExpectedNominal {
|
|
t.Fatalf("nominal received: '%v' but expected: '%v'",
|
|
movement.NominalPercentage, tt.ExpectedNominal)
|
|
}
|
|
|
|
if movement.ImpactPercentage != tt.ExpectedImpact {
|
|
t.Fatalf("impact received: '%v' but expected: '%v'",
|
|
movement.ImpactPercentage, tt.ExpectedImpact)
|
|
}
|
|
|
|
if movement.SlippageCost != tt.ExpectedCost {
|
|
t.Fatalf("cost received: '%v' but expected: '%v'",
|
|
movement.SlippageCost, tt.ExpectedCost)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetBaseAmountFromNominalSlippage(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
Name string
|
|
NominalSlippage float64
|
|
ReferencePrice float64
|
|
BidLiquidity Items
|
|
ExpectedShift *Movement
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "invalid slippage",
|
|
NominalSlippage: -1,
|
|
ExpectedError: errInvalidNominalSlippage,
|
|
},
|
|
{
|
|
Name: "invalid slippage - larger than 100%",
|
|
NominalSlippage: 101,
|
|
ExpectedError: errInvalidSlippageCannotExceed100,
|
|
},
|
|
{
|
|
Name: "no reference price",
|
|
NominalSlippage: 1,
|
|
ExpectedError: errInvalidReferencePrice,
|
|
},
|
|
{
|
|
Name: "no liquidity to service quote amount",
|
|
NominalSlippage: 1,
|
|
ReferencePrice: 1000,
|
|
ExpectedError: errNoLiquidity,
|
|
},
|
|
{
|
|
Name: "thrasher test",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
NominalSlippage: 1,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 11,
|
|
Purchased: 108900,
|
|
AverageOrderCost: 9900,
|
|
NominalPercentage: 1,
|
|
StartPrice: 10000,
|
|
EndPrice: 9800,
|
|
},
|
|
},
|
|
{
|
|
Name: "consume first tranche - take one amount out of second",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
NominalSlippage: 0.33333333333334,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 3.0000000000000275, // <- expected rounding issue
|
|
Purchased: 29900.00000000027,
|
|
AverageOrderCost: 9966.666666666664,
|
|
NominalPercentage: 0.33333333333334,
|
|
StartPrice: 10000,
|
|
EndPrice: 9900,
|
|
},
|
|
},
|
|
{
|
|
Name: "consume full liquidity",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
NominalSlippage: 10,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 12,
|
|
Purchased: 118700,
|
|
AverageOrderCost: 9891.666666666666,
|
|
NominalPercentage: 1.0833333333333395,
|
|
StartPrice: 10000,
|
|
EndPrice: 9800,
|
|
FullBookSideConsumed: true,
|
|
},
|
|
},
|
|
{
|
|
Name: "scotts lovely slippery slippage requirements",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
NominalSlippage: 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000001,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 2,
|
|
Purchased: 20000,
|
|
AverageOrderCost: 10000,
|
|
StartPrice: 10000,
|
|
EndPrice: 10000,
|
|
},
|
|
},
|
|
}
|
|
|
|
for x := range cases {
|
|
tt := cases[x]
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
depth := NewDepth(id)
|
|
depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true)
|
|
base, err := depth.bids.hitBidsByNominalSlippage(tt.NominalSlippage, tt.ReferencePrice)
|
|
if !errors.Is(err, tt.ExpectedError) {
|
|
t.Fatalf("%s received: '%v' but expected: '%v'",
|
|
tt.Name, err, tt.ExpectedError)
|
|
}
|
|
if !base.IsEqual(tt.ExpectedShift) {
|
|
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
|
tt.Name, base, tt.ExpectedShift)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// IsEqual is a tester function for comparison.
|
|
func (m *Movement) IsEqual(that *Movement) bool {
|
|
if m == nil || that == nil {
|
|
return m == nil && that == nil
|
|
}
|
|
return m.FullBookSideConsumed == that.FullBookSideConsumed &&
|
|
m.Sold == that.Sold &&
|
|
m.Purchased == that.Purchased &&
|
|
m.NominalPercentage == that.NominalPercentage &&
|
|
m.ImpactPercentage == that.ImpactPercentage &&
|
|
m.EndPrice == that.EndPrice &&
|
|
m.StartPrice == that.StartPrice &&
|
|
m.AverageOrderCost == that.AverageOrderCost
|
|
}
|
|
|
|
func TestGetBaseAmountFromImpact(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
Name string
|
|
ImpactSlippage float64
|
|
ReferencePrice float64
|
|
BidLiquidity Items
|
|
ExpectedShift *Movement
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "invalid slippage",
|
|
ExpectedError: errInvalidImpactSlippage,
|
|
},
|
|
{
|
|
Name: "invalid slippage - exceed 100%",
|
|
ImpactSlippage: 101,
|
|
ExpectedError: errInvalidSlippageCannotExceed100,
|
|
},
|
|
{
|
|
Name: "no reference price",
|
|
ImpactSlippage: 1,
|
|
ExpectedError: errInvalidReferencePrice,
|
|
},
|
|
{
|
|
Name: "no liquidity",
|
|
ImpactSlippage: 1,
|
|
ReferencePrice: 10000,
|
|
ExpectedError: errNoLiquidity,
|
|
},
|
|
{
|
|
Name: "thrasher test",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
ImpactSlippage: 1,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 2,
|
|
Purchased: 20000,
|
|
ImpactPercentage: 1,
|
|
AverageOrderCost: 10000,
|
|
StartPrice: 10000,
|
|
EndPrice: 9900,
|
|
},
|
|
},
|
|
{
|
|
Name: "consume first tranche and second tranche",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
ImpactSlippage: 2,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 9,
|
|
Purchased: 89300,
|
|
AverageOrderCost: 9922.222222222223,
|
|
ImpactPercentage: 2,
|
|
StartPrice: 10000,
|
|
EndPrice: 9800,
|
|
},
|
|
},
|
|
{
|
|
Name: "consume full liquidity",
|
|
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
|
ImpactSlippage: 10,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 12,
|
|
Purchased: 118700,
|
|
ImpactPercentage: FullLiquidityExhaustedPercentage,
|
|
AverageOrderCost: 9891.666666666666,
|
|
StartPrice: 10000,
|
|
EndPrice: 9800,
|
|
FullBookSideConsumed: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for x := range cases {
|
|
tt := cases[x]
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
depth := NewDepth(id)
|
|
depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true)
|
|
base, err := depth.bids.hitBidsByImpactSlippage(tt.ImpactSlippage, tt.ReferencePrice)
|
|
if !errors.Is(err, tt.ExpectedError) {
|
|
t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError)
|
|
}
|
|
if !base.IsEqual(tt.ExpectedShift) {
|
|
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
|
tt.Name, base, tt.ExpectedShift)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetMovementByQuoteAmount(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
Name string
|
|
QuoteAmount float64
|
|
ReferencePrice float64
|
|
AskLiquidity Items
|
|
ExpectedNominal float64
|
|
ExpectedImpact float64
|
|
ExpectedCost float64
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "no amount",
|
|
ExpectedError: errQuoteAmountInvalid,
|
|
},
|
|
{
|
|
Name: "no reference price",
|
|
QuoteAmount: 1,
|
|
ExpectedError: errInvalidReferencePrice,
|
|
},
|
|
{
|
|
Name: "not enough liquidity to service quote amount",
|
|
QuoteAmount: 1,
|
|
ReferencePrice: 1000,
|
|
ExpectedError: errNoLiquidity,
|
|
},
|
|
{
|
|
Name: "thrasher test",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
QuoteAmount: 100900,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 0.8999999999999999,
|
|
ExpectedImpact: 2,
|
|
ExpectedCost: 900,
|
|
},
|
|
{
|
|
Name: "consume first tranche",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
QuoteAmount: 20000,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 0,
|
|
ExpectedImpact: 1,
|
|
ExpectedCost: 0,
|
|
},
|
|
{
|
|
Name: "consume most of first tranche",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
QuoteAmount: 15000,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 0,
|
|
ExpectedImpact: 0,
|
|
ExpectedCost: 0,
|
|
},
|
|
{
|
|
Name: "consume full liquidity",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
QuoteAmount: 121300,
|
|
ReferencePrice: 10000,
|
|
ExpectedNominal: 1.0833333333333395,
|
|
ExpectedImpact: FullLiquidityExhaustedPercentage,
|
|
ExpectedCost: 1300,
|
|
},
|
|
}
|
|
|
|
for x := range cases {
|
|
tt := cases[x]
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
depth := NewDepth(id)
|
|
depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true)
|
|
movement, err := depth.asks.getMovementByQuotation(tt.QuoteAmount, tt.ReferencePrice, false)
|
|
if !errors.Is(err, tt.ExpectedError) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError)
|
|
}
|
|
|
|
if movement == nil {
|
|
return
|
|
}
|
|
if movement.NominalPercentage != tt.ExpectedNominal {
|
|
t.Fatalf("nominal received: '%v' but expected: '%v'",
|
|
movement.NominalPercentage, tt.ExpectedNominal)
|
|
}
|
|
|
|
if movement.ImpactPercentage != tt.ExpectedImpact {
|
|
t.Fatalf("impact received: '%v' but expected: '%v'",
|
|
movement.ImpactPercentage, tt.ExpectedImpact)
|
|
}
|
|
|
|
if movement.SlippageCost != tt.ExpectedCost {
|
|
t.Fatalf("cost received: '%v' but expected: '%v'",
|
|
movement.SlippageCost, tt.ExpectedCost)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetQuoteAmountFromNominalSlippage(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
Name string
|
|
NominalSlippage float64
|
|
ReferencePrice float64
|
|
AskLiquidity Items
|
|
ExpectedShift *Movement
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "invalid slippage",
|
|
NominalSlippage: -1,
|
|
ExpectedError: errInvalidNominalSlippage,
|
|
},
|
|
{
|
|
Name: "no reference price",
|
|
NominalSlippage: 1,
|
|
ExpectedError: errInvalidReferencePrice,
|
|
},
|
|
{
|
|
Name: "no liquidity",
|
|
NominalSlippage: 1,
|
|
ReferencePrice: 10000,
|
|
ExpectedError: errNoLiquidity,
|
|
},
|
|
{
|
|
Name: "consume first tranche - one amount on second tranche",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
NominalSlippage: 0.33333333333334,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 30100.000000000276, // <- expected rounding issue
|
|
Purchased: 3.0000000000000275,
|
|
AverageOrderCost: 10033.333333333333333333333333333,
|
|
NominalPercentage: 0.33333333333334,
|
|
StartPrice: 10000,
|
|
EndPrice: 10100,
|
|
},
|
|
},
|
|
{
|
|
Name: "last tranche total agg meeting 1 percent nominally",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
NominalSlippage: 1,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 111100,
|
|
Purchased: 11,
|
|
AverageOrderCost: 10100,
|
|
NominalPercentage: 1,
|
|
StartPrice: 10000,
|
|
EndPrice: 10200,
|
|
},
|
|
},
|
|
{
|
|
Name: "take full second tranche",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
NominalSlippage: 0.7777777777777738,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 90700,
|
|
Purchased: 9,
|
|
AverageOrderCost: 10077.777777777777,
|
|
NominalPercentage: 0.7777777777777738,
|
|
StartPrice: 10000,
|
|
EndPrice: 10100,
|
|
},
|
|
},
|
|
{
|
|
Name: "consume full liquidity",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
NominalSlippage: 10,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 121300,
|
|
Purchased: 12,
|
|
AverageOrderCost: 10108.333333333334,
|
|
NominalPercentage: 1.0833333333333395,
|
|
StartPrice: 10000,
|
|
EndPrice: 10200,
|
|
FullBookSideConsumed: true,
|
|
},
|
|
},
|
|
{
|
|
Name: "scotts lovely slippery slippage requirements",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
NominalSlippage: 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000001,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 20000,
|
|
Purchased: 2,
|
|
AverageOrderCost: 10000,
|
|
StartPrice: 10000,
|
|
EndPrice: 10000,
|
|
},
|
|
},
|
|
}
|
|
|
|
for x := range cases {
|
|
tt := cases[x]
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
depth := NewDepth(id)
|
|
depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true)
|
|
quote, err := depth.asks.liftAsksByNominalSlippage(tt.NominalSlippage, tt.ReferencePrice)
|
|
if !errors.Is(err, tt.ExpectedError) {
|
|
t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError)
|
|
}
|
|
if !quote.IsEqual(tt.ExpectedShift) {
|
|
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
|
tt.Name, quote, tt.ExpectedShift)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetQuoteAmountFromImpact(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
Name string
|
|
ImpactSlippage float64
|
|
ReferencePrice float64
|
|
AskLiquidity Items
|
|
ExpectedShift *Movement
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "invalid slippage",
|
|
ImpactSlippage: -1,
|
|
ExpectedError: errInvalidImpactSlippage,
|
|
},
|
|
{
|
|
Name: "no reference price",
|
|
ImpactSlippage: 1,
|
|
ExpectedError: errInvalidReferencePrice,
|
|
},
|
|
{
|
|
Name: "no liquidity",
|
|
ImpactSlippage: 1,
|
|
ReferencePrice: 1000,
|
|
ExpectedError: errNoLiquidity,
|
|
},
|
|
{
|
|
Name: "thrasher test",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
ImpactSlippage: 1,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 20000,
|
|
Purchased: 2,
|
|
AverageOrderCost: 10000,
|
|
ImpactPercentage: 1,
|
|
StartPrice: 10000,
|
|
EndPrice: 10100,
|
|
},
|
|
},
|
|
{
|
|
Name: "consume first tranche and second tranche",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
ImpactSlippage: 2,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 90700,
|
|
Purchased: 9,
|
|
AverageOrderCost: 10077.777777777777777777777777778,
|
|
ImpactPercentage: 2,
|
|
StartPrice: 10000,
|
|
EndPrice: 10200,
|
|
},
|
|
},
|
|
{
|
|
Name: "consume full liquidity",
|
|
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
|
ImpactSlippage: 10,
|
|
ReferencePrice: 10000,
|
|
ExpectedShift: &Movement{
|
|
Sold: 121300,
|
|
Purchased: 12,
|
|
AverageOrderCost: 10108.333333333333333333333333333,
|
|
ImpactPercentage: FullLiquidityExhaustedPercentage,
|
|
StartPrice: 10000,
|
|
EndPrice: 10200,
|
|
FullBookSideConsumed: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for x := range cases {
|
|
tt := cases[x]
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
depth := NewDepth(id)
|
|
depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true)
|
|
quote, err := depth.asks.liftAsksByImpactSlippage(tt.ImpactSlippage, tt.ReferencePrice)
|
|
if !errors.Is(err, tt.ExpectedError) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError)
|
|
}
|
|
if !quote.IsEqual(tt.ExpectedShift) {
|
|
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
|
tt.Name, quote, tt.ExpectedShift)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetHeadPrice(t *testing.T) {
|
|
t.Parallel()
|
|
depth := NewDepth(id)
|
|
if _, err := depth.bids.getHeadPriceNoLock(); !errors.Is(err, errNoLiquidity) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
|
}
|
|
if _, err := depth.asks.getHeadPriceNoLock(); !errors.Is(err, errNoLiquidity) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
|
}
|
|
depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
|
|
|
|
val, err := depth.bids.getHeadPriceNoLock()
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if val != 1336 {
|
|
t.Fatal("unexpected value")
|
|
}
|
|
|
|
val, err = depth.asks.getHeadPriceNoLock()
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
if val != 1337 {
|
|
t.Fatal("unexpected value", val)
|
|
}
|
|
}
|
|
|
|
func TestFinalizeFields(t *testing.T) {
|
|
m := &Movement{}
|
|
_, err := m.finalizeFields(0, 0, 0, 0, false)
|
|
if !errors.Is(err, errInvalidCost) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidCost)
|
|
}
|
|
_, err = m.finalizeFields(1, 0, 0, 0, false)
|
|
if !errors.Is(err, errInvalidAmount) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidAmount)
|
|
}
|
|
_, err = m.finalizeFields(1, 1, 0, 0, false)
|
|
if !errors.Is(err, errInvalidHeadPrice) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidHeadPrice)
|
|
}
|
|
|
|
// Test slippage as per https://en.wikipedia.org/wiki/Slippage_(finance)
|
|
mov, err := m.finalizeFields(20000*151.11585, 20000, 151.08, 0, false)
|
|
if !errors.Is(err, nil) {
|
|
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
|
}
|
|
|
|
// These tests demonstrate the imprecision of relying on floating point numbers
|
|
// That a different OS will return different numbers: macOS: `716.9999999997499` vs '716.9999999995343'
|
|
// speed is important, but having tests look for exact floating point numbers shows that one
|
|
// could have a different impact simply from running it on a different computer
|
|
if mov.SlippageCost != 716.9999999995343 {
|
|
t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 716.9999999995343)
|
|
}
|
|
}
|