Last active
January 5, 2020 22:13
-
-
Save ewancook/73080e7e42536579910ce17a186484a4 to your computer and use it in GitHub Desktop.
Order Book Updating (Kraken)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package orderengine | |
import ( | |
"sort" | |
) | |
// Book represents an order book | |
type Book []*Order | |
// Order is a single order to be placed | |
type Order struct { | |
Price, Volume float64 | |
} | |
// NewBook returns a new order book (Book) | |
func NewBook(maxDepth uint) Book { | |
return make(Book, 0, maxDepth+1) | |
} | |
func (b *Book) removeOrder(order *Order, index int) { | |
length := len(*b) | |
if index < length-1 { | |
copy((*b)[index:], (*b)[index+1:]) | |
} | |
(*b)[length-1] = nil | |
*b = (*b)[:length-1] | |
} | |
func (b *Book) insertOrder(order *Order, index int) { | |
*b = append(*b, nil) | |
copy((*b)[index+1:], (*b)[index:]) | |
(*b)[index] = order | |
if length := len(*b); length == cap(*b) { | |
*b = (*b)[:length-1] | |
} | |
} | |
func (b *Book) replaceOrder(order *Order, index int) { | |
(*b)[index] = order | |
} | |
// Update adds the orders supplied to the order book | |
func (b *Book) Update(orders ...*Order) { | |
for _, order := range orders { | |
length := len(*b) | |
index := sort.Search(length, func(i int) bool { | |
return order.Price <= (*b)[i].Price | |
}) | |
if index >= length { | |
continue | |
} else if order.Volume == 0 { | |
b.removeOrder(order, index) | |
} else if order.Price == (*b)[index].Price { | |
b.replaceOrder(order, index) | |
} else { | |
b.insertOrder(order, index) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package orderengine | |
import ( | |
"testing" | |
) | |
func _newBook() *Book { | |
book := append(NewBook(10), | |
&Order{Price: 5706.30000, Volume: 1.00000000}, | |
&Order{Price: 5706.40000, Volume: 0.85600000}, | |
&Order{Price: 5706.90000, Volume: 1.17300000}, | |
&Order{Price: 5707.00000, Volume: 0.00200000}, | |
&Order{Price: 5707.40000, Volume: 4.33000000}, | |
&Order{Price: 5707.80000, Volume: 2.50000000}, | |
&Order{Price: 5708.20000, Volume: 5.00000000}, | |
&Order{Price: 5708.30000, Volume: 0.75483907}, | |
&Order{Price: 5709.20000, Volume: 3.30000000}, | |
&Order{Price: 5711.70000, Volume: 0.00749800}) | |
return &book | |
} | |
func TestInsertOrder(t *testing.T) { | |
book := _newBook() | |
index := 3 | |
startLen, startCap := len(*book), cap(*book) | |
newOrder := &Order{Price: 5712.85, Volume: 1.0} | |
book.insertOrder(newOrder, index) | |
if (*book)[index] != newOrder { | |
t.Fatal("incorrect index for inserted order") | |
} | |
if len(*book) != startLen { | |
t.Fatalf("length of book changed (%d != %d)", len(*book), startLen) | |
} | |
if cap(*book) != startCap { | |
t.Fatalf("capacity of book changed (%d != %d)", cap(*book), startCap) | |
} | |
} | |
func TestRemoveOrder(t *testing.T) { | |
book := _newBook() | |
indexToRemove := 4 | |
newFirstOrder := (*book)[indexToRemove+1] | |
startLen, startCap := len(*book), cap(*book) | |
book.removeOrder((*book)[indexToRemove], indexToRemove) | |
if len(*book) != startLen-1 { | |
t.Fatalf("length of book didn't decrease by one (%d != %d)", len(*book), startLen-1) | |
} | |
if cap(*book) != startCap { | |
t.Fatalf("capacity of book changed (%d != %d)", cap(*book), startCap) | |
} | |
if (*book)[indexToRemove] != newFirstOrder { | |
t.Fatal("slice not restuctured") | |
} | |
} | |
func TestReplaceOrder(t *testing.T) { | |
book := _newBook() | |
indexToChange, newVol := 0, 1.0 | |
newOrder := &Order{(*book)[indexToChange].Price, newVol} | |
book.replaceOrder(newOrder, indexToChange) | |
if (*book)[indexToChange] != newOrder { | |
t.Fatal("order not changed") | |
} | |
} | |
func TestNewBook(t *testing.T) { | |
var length uint = 3 | |
book := NewBook(length) | |
if cap(book) != int(length)+1 { | |
t.Fatalf("capacities not equal (%d != %d)\n", len(book), length+1) | |
} | |
} | |
func TestUpdateBook(t *testing.T) { | |
book := _newBook() | |
book.Update(&Order{Price: 5709.20000, Volume: 3.00000000}, | |
&Order{Price: 5708.20000, Volume: 0.00000000}, | |
&Order{Price: 5705.90000, Volume: 7.62400000}) | |
book.Update(&Order{Price: 5709.20000, Volume: 8.00000000}, | |
&Order{Price: 5709.40000, Volume: 0.30000000}) | |
book.Update(&Order{Price: 5708.30000, Volume: 0.00000000}, | |
&Order{Price: 5705.90000, Volume: 7.62400000}) | |
prices := []float64{5705.9, 5706.3, 5706.4, 5706.9, 5707.0, 5707.4, 5707.8, 5709.2, 5709.4, 5711.7} | |
vols := []float64{7.624, 1.0, 0.856, 1.173, 0.002, 4.33, 2.5, 8.0, 0.3, 0.007498} | |
for i, order := range *book { | |
if prices[i] != order.Price { | |
t.Fatalf("wrong price in orderbook (%f != %f)", prices[i], order.Price) | |
} | |
if vols[i] != order.Volume { | |
t.Fatalf("wrong volume in orderbook (%f != %f)", vols[i], order.Volume) | |
} | |
} | |
} | |
func TestRepeatedInsert(t *testing.T) { | |
book := _newBook() | |
startLen, startCap := len(*book), cap(*book) | |
newOrders := []*Order{&Order{Price: 5707.85, Volume: 1.0}, | |
&Order{Price: 5707.86, Volume: 1.0}, | |
&Order{Price: 5707.87, Volume: 1.0}} | |
book.Update(newOrders...) | |
if len(*book) != startLen { | |
t.Fatalf("length of book changed (%d != %d)", len(*book), startLen) | |
} | |
if cap(*book) != startCap { | |
t.Fatalf("capacity of book changed (%d != %d)", cap(*book), startCap) | |
} | |
} | |
func TestOutOfRangeUpdate(t *testing.T) { | |
book := _newBook() | |
newOrder := &Order{Price: 5712.85, Volume: 1.0} | |
book.Update(newOrder) | |
for _, order := range *book { | |
if order == newOrder { | |
t.Fatal("out of range order in order book") | |
} | |
} | |
} | |
func Benchmark_newBook(b *testing.B) { | |
for n := 0; n < b.N; n++ { | |
_ = _newBook() | |
} | |
} | |
func BenchmarkUpdateBook(b *testing.B) { | |
first := []*Order{&Order{Price: 5709.20000, Volume: 3.00000000}, | |
&Order{Price: 5708.20000, Volume: 0.00000000}, | |
&Order{Price: 5705.90000, Volume: 7.62400000}} | |
second := []*Order{&Order{Price: 5709.20000, Volume: 8.00000000}, | |
&Order{Price: 5709.40000, Volume: 0.30000000}} | |
third := []*Order{&Order{Price: 5708.30000, Volume: 0.00000000}, | |
&Order{Price: 5705.90000, Volume: 7.62400000}} | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
book := _newBook() | |
book.Update(first...) | |
book.Update(second...) | |
book.Update(third...) | |
} | |
} | |
func BenchmarkSingleInsert(b *testing.B) { | |
order := &Order{Price: 5705.90000, Volume: 7.62400000} | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
book := _newBook() | |
book.insertOrder(order, 0) | |
} | |
} | |
func BenchmarkSingleRemove(b *testing.B) { | |
order := &Order{Price: 5706.30000, Volume: 0.0} | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
book := _newBook() | |
book.removeOrder(order, 0) | |
} | |
} | |
func BenchmarkSingleReplace(b *testing.B) { | |
order := &Order{Price: 5706.30000, Volume: 1.0} | |
b.ResetTimer() | |
for n := 0; n < b.N; n++ { | |
book := _newBook() | |
book.replaceOrder(order, 0) | |
} | |
} | |
func BenchmarkNewBook(b *testing.B) { | |
var maxDepth uint = 10 | |
for n := 0; n < b.N; n++ { | |
_ = NewBook(maxDepth) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment