GCT: general updates across codebase (#699)

* orderbook: export orderbook nodes for external strategy inspection

* orderbook: Add in methods for locking and unlocking multiple books at the same time e.g. book1.LockWith(book2); defer book1.UnlockWith(book2)

* include waiting functionality for depth change alert

* backtester: add word.

* log: include logger changes to impl with downstream integration

* engine: reduce params for loading exchange

* assort: rm verbose in tests, change wording in ob, expose sync.waitgroup for ext. sync options

* ticker: reduce map look ups and contention when using RW mutex when there are over 80% writes adds find last function to get the latest rate

* engine/syncmanager: add in waitgroup for step over for external package calls

* cleaup

* engine: linter fix

* currency/fx: include all references to fiat currencies to default

* orderbook: Add in fields to Unsafe type for strategies to detect potential out of sync book operations

* syncmanager: changed config variable to display correct time

* ordermanager: Add time when none provided

* currency/manager: update getasset param to get enabled assets for minor optimizations

* ftx: use get all wallet balances for a better accounts breakdown

* orderbook: unlock in reverse order

* bithumb: fixes bug on market buy and sell orders

* bithumb: fix bug for nonce is also time window sensitive

* bithumb: get orders add required parameter

* bithumb: Add asset type to account struct

* currency: improve log output when checking currency and it fails

* bithumb: Add error return on incomplete pair

* ticker:unexport all service related methods

* ticker/currency: fixes

* orderbook: fix comment

* engine: revert variable name in LoadExchange method

* sync_manager: fix panic when enabling disabling manager

* engine: fix naming convention of exported function and comments

* engine: update comment

* orderbook: fix comment for unsafe type
This commit is contained in:
Ryan O'Hara-Reid
2021-07-29 14:42:28 +10:00
committed by GitHub
parent 4f5ab42bd8
commit a2381310da
64 changed files with 842 additions and 562 deletions

View File

@@ -13,7 +13,7 @@ var errAmountCannotBeLessOrEqualToZero = errors.New("amount cannot be less or eq
// to and from a stack.
type linkedList struct {
length int
head *node
head *Node
}
// comparison defines expected functionality to compare between two reference
@@ -27,43 +27,43 @@ func (ll *linkedList) load(items Items, stack *stack) {
// Tip sets up a pointer to a struct field variable pointer. This is used
// so when a node is popped from the stack we can reference that current
// nodes' struct 'next' field and set on next iteration without utilising
// assignment for example `prev.next = *node`.
// assignment for example `prev.Next = *node`.
var tip = &ll.head
// Prev denotes a place holder to node and all of its next references need
// to be pushed back onto stack.
var prev *node
var prev *Node
for i := range items {
if *tip == nil {
// Extend node chain
*tip = stack.Pop()
// Set current node prev to last node
(*tip).prev = prev
(*tip).Prev = prev
ll.length++
}
// Set item value
(*tip).value = items[i]
(*tip).Value = items[i]
// Set previous to current node
prev = *tip
// Set tip to next node
tip = &(*tip).next
tip = &(*tip).Next
}
// Push has references to dangling nodes that need to be removed and pushed
// back onto stack for re-use
var push *node
var push *Node
// Cleave unused reference chain from main chain
if prev == nil {
// The entire chain will need to be pushed back on to stack
push = *tip
ll.head = nil
} else {
push = prev.next
prev.next = nil
push = prev.Next
prev.Next = nil
}
// Push unused pointers back on stack
for push != nil {
pending := push.next
pending := push.Next
stack.Push(push, getNow())
ll.length--
push = pending
@@ -74,16 +74,16 @@ func (ll *linkedList) load(items Items, stack *stack) {
func (ll *linkedList) updateByID(updts []Item) error {
updates:
for x := range updts {
for tip := ll.head; tip != nil; tip = tip.next {
if updts[x].ID != tip.value.ID { // Filter IDs that don't match
for tip := ll.head; tip != nil; tip = tip.Next {
if updts[x].ID != tip.Value.ID { // Filter IDs that don't match
continue
}
if updts[x].Price > 0 {
// Only apply changes when zero values are not present, Bitmex
// for example sends 0 price values.
tip.value.Price = updts[x].Price
tip.Value.Price = updts[x].Price
}
tip.value.Amount = updts[x].Amount
tip.Value.Amount = updts[x].Amount
continue updates
}
return fmt.Errorf("update error: %w %d not found",
@@ -97,8 +97,8 @@ updates:
func (ll *linkedList) deleteByID(updts Items, stack *stack, bypassErr bool) error {
updates:
for x := range updts {
for tip := &ll.head; *tip != nil; tip = &(*tip).next {
if updts[x].ID != (*tip).value.ID {
for tip := &ll.head; *tip != nil; tip = &(*tip).Next {
if updts[x].ID != (*tip).Value.ID {
continue
}
stack.Push(deleteAtTip(ll, tip), getNow())
@@ -122,15 +122,15 @@ func (ll *linkedList) cleanup(maxChainLength int, stack *stack) {
// cleaved after that update, new update might not applied correctly.
n := ll.head
for i := 0; i < maxChainLength; i++ {
if n.next == nil {
if n.Next == nil {
return
}
n = n.next
n = n.Next
}
// cleave reference to current node
if n.prev != nil {
n.prev.next = nil
if n.Prev != nil {
n.Prev.Next = nil
} else {
ll.head = nil
}
@@ -138,7 +138,7 @@ func (ll *linkedList) cleanup(maxChainLength int, stack *stack) {
var pruned int
for n != nil {
pruned++
pending := n.next
pending := n.Next
stack.Push(n, getNow())
n = pending
}
@@ -147,9 +147,9 @@ func (ll *linkedList) cleanup(maxChainLength int, stack *stack) {
// amount returns total depth liquidity and value
func (ll *linkedList) amount() (liquidity, value float64) {
for tip := ll.head; tip != nil; tip = tip.next {
liquidity += tip.value.Amount
value += tip.value.Amount * tip.value.Price
for tip := ll.head; tip != nil; tip = tip.Next {
liquidity += tip.Value.Amount
value += tip.Value.Amount * tip.Value.Price
}
return
}
@@ -158,8 +158,8 @@ func (ll *linkedList) amount() (liquidity, value float64) {
func (ll *linkedList) retrieve() Items {
depth := make(Items, ll.length)
iterator := 0
for tip := ll.head; tip != nil; tip = tip.next {
depth[iterator] = tip.value
for tip := ll.head; tip != nil; tip = tip.Next {
depth[iterator] = tip.Value
iterator++
}
return depth
@@ -169,21 +169,21 @@ func (ll *linkedList) retrieve() Items {
// updates
func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLength int, compare func(float64, float64) bool, tn now) {
for x := range updts {
for tip := &ll.head; ; tip = &(*tip).next {
for tip := &ll.head; ; tip = &(*tip).Next {
if *tip == nil {
insertHeadSpecific(ll, updts[x], stack)
break
}
if (*tip).value.Price == updts[x].Price { // Match check
if (*tip).Value.Price == updts[x].Price { // Match check
if updts[x].Amount <= 0 { // Capture delete update
stack.Push(deleteAtTip(ll, tip), tn)
} else { // Amend current amount value
(*tip).value.Amount = updts[x].Amount
(*tip).Value.Amount = updts[x].Amount
}
break // Continue updates
}
if compare((*tip).value.Price, updts[x].Price) { // Insert
if compare((*tip).Value.Price, updts[x].Price) { // Insert
// This check below filters zero values and provides an
// optimisation for when select exchanges send a delete update
// to a non-existent price level (OTC/Hidden order) so we can
@@ -194,7 +194,7 @@ func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLen
break // Continue updates
}
if (*tip).next == nil { // Tip is at tail
if (*tip).Next == nil { // Tip is at tail
// This check below is just a catch all in the event the above
// zero value check fails
if updts[x].Amount > 0 {
@@ -229,14 +229,14 @@ updates:
// from the stack and then pushing to the stack later for cleanup.
// If the ID is not found we can pop from stack then insert into that
// price level
var bookmark *node
for tip := ll.head; tip != nil; tip = tip.next {
if tip.value.ID == updts[x].ID {
if tip.value.Price != updts[x].Price { // Price level change
if tip.next == nil {
var bookmark *Node
for tip := ll.head; tip != nil; tip = tip.Next {
if tip.Value.ID == updts[x].ID {
if tip.Value.Price != updts[x].Price { // Price level change
if tip.Next == nil {
// no movement needed just a re-adjustment
tip.value.Price = updts[x].Price
tip.value.Amount = updts[x].Amount
tip.Value.Price = updts[x].Price
tip.Value.Amount = updts[x].Amount
continue updates
}
// bookmark tip to move this node to correct price level
@@ -244,55 +244,55 @@ updates:
continue // continue through node depth
}
// no price change, amend amount and continue update
tip.value.Amount = updts[x].Amount
tip.Value.Amount = updts[x].Amount
continue updates // continue to next update
}
if compare(tip.value.Price, updts[x].Price) {
if compare(tip.Value.Price, updts[x].Price) {
if bookmark != nil { // shift bookmarked node to current tip
bookmark.value = updts[x]
bookmark.Value = updts[x]
move(&ll.head, bookmark, tip)
continue updates
}
// search for ID
for n := tip.next; n != nil; n = n.next {
if n.value.ID == updts[x].ID {
n.value = updts[x]
for n := tip.Next; n != nil; n = n.Next {
if n.Value.ID == updts[x].ID {
n.Value = updts[x]
// inserting before the tip
move(&ll.head, n, tip)
continue updates
}
}
// ID not matched in depth so add correct level for insert
if tip.next == nil {
if tip.Next == nil {
n := stack.Pop()
n.value = updts[x]
n.Value = updts[x]
ll.length++
if tip.prev == nil {
tip.prev = n
n.next = tip
if tip.Prev == nil {
tip.Prev = n
n.Next = tip
ll.head = n
continue updates
}
tip.prev.next = n
n.prev = tip.prev
tip.prev = n
n.next = tip
tip.Prev.Next = n
n.Prev = tip.Prev
tip.Prev = n
n.Next = tip
continue updates
}
bookmark = tip
break
}
if tip.next == nil {
if tip.Next == nil {
if shiftBookmark(tip, &bookmark, &ll.head, updts[x]) {
continue updates
}
}
}
n := stack.Pop()
n.value = updts[x]
n.Value = updts[x]
insertNodeAtBookmark(ll, bookmark, n) // Won't inline with stack
}
return nil
@@ -301,38 +301,38 @@ updates:
// insertUpdates inserts new updates for bids or asks based on price level
func (ll *linkedList) insertUpdates(updts Items, stack *stack, comp comparison) error {
for x := range updts {
var prev *node
for tip := &ll.head; ; tip = &(*tip).next {
var prev *Node
for tip := &ll.head; ; tip = &(*tip).Next {
if *tip == nil { // Head
n := stack.Pop()
n.value = updts[x]
n.prev = prev
n.Value = updts[x]
n.Prev = prev
ll.length++
*tip = n
break // Continue updates
}
if (*tip).value.Price == updts[x].Price { // Price already found
if (*tip).Value.Price == updts[x].Price { // Price already found
return fmt.Errorf("%w for price %f",
errCollisionDetected,
updts[x].Price)
}
if comp((*tip).value.Price, updts[x].Price) { // Alignment
if comp((*tip).Value.Price, updts[x].Price) { // Alignment
n := stack.Pop()
n.value = updts[x]
n.prev = prev
n.Value = updts[x]
n.Prev = prev
ll.length++
// Reference current with new node
(*tip).prev = n
(*tip).Prev = n
// Push tip to the right
n.next = *tip
// This is the same as prev.next = n
n.Next = *tip
// This is the same as prev.Next = n
*tip = n
break // Continue updates
}
if (*tip).next == nil { // Tail
if (*tip).Next == nil { // Tail
insertAtTail(ll, tip, updts[x], stack)
break // Continue updates
}
@@ -399,80 +399,80 @@ func (ll *asks) insertUpdates(updts Items, stack *stack) error {
// move moves a node from a point in a node chain to another node position,
// this left justified towards head as element zero is the top of the depth
// side. (can inline)
func move(head **node, from, to *node) {
if from.next != nil { // From is at tail
from.next.prev = from.prev
func move(head **Node, from, to *Node) {
if from.Next != nil { // From is at tail
from.Next.Prev = from.Prev
}
if from.prev == nil { // From is at head
(*head).next.prev = nil
*head = (*head).next
if from.Prev == nil { // From is at head
(*head).Next.Prev = nil
*head = (*head).Next
} else {
from.prev.next = from.next
from.Prev.Next = from.Next
}
// insert from node next to 'to' node
if to.prev == nil { // Destination is at head position
if to.Prev == nil { // Destination is at head position
*head = from
} else {
to.prev.next = from
to.Prev.Next = from
}
from.prev = to.prev
to.prev = from
from.next = to
from.Prev = to.Prev
to.Prev = from
from.Next = to
}
// deleteAtTip removes a node from tip target returns old node (can inline)
func deleteAtTip(ll *linkedList, tip **node) *node {
func deleteAtTip(ll *linkedList, tip **Node) *Node {
// Old is a placeholder for current tips node value to push
// back on to the stack.
old := *tip
switch {
case old.prev == nil: // At head position
case old.Prev == nil: // At head position
// shift current tip head to the right
*tip = old.next
*tip = old.Next
// Remove reference to node from chain
if old.next != nil { // This is when liquidity hits zero
old.next.prev = nil
if old.Next != nil { // This is when liquidity hits zero
old.Next.Prev = nil
}
case old.next == nil: // At tail position
case old.Next == nil: // At tail position
// Remove reference to node from chain
old.prev.next = nil
old.Prev.Next = nil
default:
// Reference prior node in chain to next node in chain
// bypassing current node
old.prev.next = old.next
old.next.prev = old.prev
old.Prev.Next = old.Next
old.Next.Prev = old.Prev
}
ll.length--
return old
}
// insertAtTip inserts at a tip target (can inline)
func insertAtTip(ll *linkedList, tip **node, updt Item, stack *stack) {
func insertAtTip(ll *linkedList, tip **Node, updt Item, stack *stack) {
n := stack.Pop()
n.value = updt
n.next = *tip
n.prev = (*tip).prev
if (*tip).prev == nil { // Tip is at head
n.Value = updt
n.Next = *tip
n.Prev = (*tip).Prev
if (*tip).Prev == nil { // Tip is at head
// Replace head which will push everything to the right
// when this node will reference new node below
*tip = n
} else {
// Reference new node to previous node
(*tip).prev.next = n
(*tip).Prev.Next = n
}
// Reference next node to new node
n.next.prev = n
n.Next.Prev = n
ll.length++
}
// insertAtTail inserts at tail end of node chain (can inline)
func insertAtTail(ll *linkedList, tip **node, updt Item, stack *stack) {
func insertAtTail(ll *linkedList, tip **Node, updt Item, stack *stack) {
n := stack.Pop()
n.value = updt
n.Value = updt
// Reference tip to new node
(*tip).next = n
(*tip).Next = n
// Reference new node with current tip
n.prev = *tip
n.Prev = *tip
ll.length++
}
@@ -481,50 +481,50 @@ func insertAtTail(ll *linkedList, tip **node, updt Item, stack *stack) {
// endpoint then it comes back online. (can inline)
func insertHeadSpecific(ll *linkedList, updt Item, stack *stack) {
n := stack.Pop()
n.value = updt
n.Value = updt
ll.head = n
ll.length++
}
// insertNodeAtBookmark inserts a new node at a bookmarked node position
// returns if a node needs to replace head (can inline)
func insertNodeAtBookmark(ll *linkedList, bookmark, n *node) {
func insertNodeAtBookmark(ll *linkedList, bookmark, n *Node) {
switch {
case bookmark == nil: // Zero liquidity and we are rebuilding from scratch
ll.head = n
case bookmark.prev == nil:
n.prev = bookmark.prev
bookmark.prev = n
n.next = bookmark
case bookmark.Prev == nil:
n.Prev = bookmark.Prev
bookmark.Prev = n
n.Next = bookmark
ll.head = n
case bookmark.next == nil:
n.prev = bookmark
bookmark.next = n
case bookmark.Next == nil:
n.Prev = bookmark
bookmark.Next = n
default:
bookmark.prev.next = n
n.prev = bookmark.prev
bookmark.prev = n
n.next = bookmark
bookmark.Prev.Next = n
n.Prev = bookmark.Prev
bookmark.Prev = n
n.Next = bookmark
}
ll.length++
}
// shiftBookmark moves a bookmarked node to the tip's next position or if nil,
// sets tip as bookmark (can inline)
func shiftBookmark(tip *node, bookmark, head **node, updt Item) bool {
func shiftBookmark(tip *Node, bookmark, head **Node, updt Item) bool {
if *bookmark == nil { // End of the chain and no bookmark set
*bookmark = tip // Set tip to bookmark so we can set a new node there
return false
}
(*bookmark).value = updt
(*bookmark).next.prev = (*bookmark).prev
if (*bookmark).prev == nil { // Bookmark is at head
*head = (*bookmark).next
(*bookmark).Value = updt
(*bookmark).Next.Prev = (*bookmark).Prev
if (*bookmark).Prev == nil { // Bookmark is at head
*head = (*bookmark).Next
} else {
(*bookmark).prev.next = (*bookmark).next
(*bookmark).Prev.Next = (*bookmark).Next
}
tip.next = *bookmark
(*bookmark).prev = tip
(*bookmark).next = nil
tip.Next = *bookmark
(*bookmark).Prev = tip
(*bookmark).Next = nil
return true
}

View File

@@ -32,7 +32,7 @@ var ask = Items{
// Display displays depth content for tests
func (ll *linkedList) display() {
for tip := ll.head; tip != nil; tip = tip.next {
for tip := ll.head; tip != nil; tip = tip.Next {
fmt.Printf("NODE: %+v %p \n", tip, tip)
}
fmt.Println()
@@ -1261,23 +1261,23 @@ func Check(depth interface{}, liquidity, value float64, nodeCount int, t *testin
return
}
var tail *node
var tail *Node
var price float64
for tip := ll.head; ; tip = tip.next {
for tip := ll.head; ; tip = tip.Next {
switch {
case price == 0:
price = tip.value.Price
case isBid && price < tip.value.Price:
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:
case isAsk && price > tip.Value.Price:
ll.display()
t.Fatal("Ask pricing out of order should be ascending")
default:
price = tip.value.Price
price = tip.Value.Price
}
if tip.next == nil {
if tip.Next == nil {
tail = tip
break
}
@@ -1285,9 +1285,9 @@ func Check(depth interface{}, liquidity, value float64, nodeCount int, t *testin
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
for tip := tail; tip != nil; tip = tip.Prev {
liqReversed += tip.Value.Amount
valReversed += tip.Value.Amount * tip.Value.Price
nodeReversed++
}
@@ -1339,92 +1339,92 @@ func TestAmount(t *testing.T) {
}
func TestShiftBookmark(t *testing.T) {
bookmarkedNode := &node{
value: Item{
bookmarkedNode := &Node{
Value: Item{
ID: 1337,
Amount: 1,
Price: 2,
},
next: nil,
prev: nil,
Next: nil,
Prev: nil,
shelved: time.Time{},
}
originalBookmarkPrev := &node{
value: Item{
originalBookmarkPrev := &Node{
Value: Item{
ID: 1336,
},
next: bookmarkedNode,
prev: nil, // At head
Next: bookmarkedNode,
Prev: nil, // At head
shelved: time.Time{},
}
originalBookmarkNext := &node{
value: Item{
originalBookmarkNext := &Node{
Value: Item{
ID: 1338,
},
next: nil, // This can be left nil in actuality this will be
Next: nil, // This can be left nil in actuality this will be
// populated
prev: bookmarkedNode,
Prev: bookmarkedNode,
shelved: time.Time{},
}
// associate previous and next nodes to bookmarked node
bookmarkedNode.prev = originalBookmarkPrev
bookmarkedNode.next = originalBookmarkNext
bookmarkedNode.Prev = originalBookmarkPrev
bookmarkedNode.Next = originalBookmarkNext
tip := &node{
value: Item{
tip := &Node{
Value: Item{
ID: 69420,
},
next: nil, // In this case tip will be at tail
prev: nil,
Next: nil, // In this case tip will be at tail
Prev: nil,
shelved: time.Time{},
}
tipprev := &node{
value: Item{
tipprev := &Node{
Value: Item{
ID: 69419,
},
next: tip,
prev: nil, // This can be left nil in actuality this will be
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
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 {
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 {
if bookmarkedNode.Prev != tip {
t.Fatal("bookmarked prev memory address does not point to tip")
}
if bookmarkedNode.next != nil {
if bookmarkedNode.Next != nil {
t.Fatal("bookmarked next is at tail and should be nil")
}
if bookmarkedNode.next != nil {
if bookmarkedNode.Next != nil {
t.Fatal("bookmarked next is at tail and should be nil")
}
if originalBookmarkPrev.next != originalBookmarkNext {
if originalBookmarkPrev.Next != originalBookmarkNext {
t.Fatal("original bookmarked prev node should be associated with original bookmarked next node")
}
if originalBookmarkNext.prev != originalBookmarkPrev {
if originalBookmarkNext.Prev != originalBookmarkPrev {
t.Fatal("original bookmarked next node should be associated with original bookmarked prev node")
}
var nilBookmark *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")
@@ -1435,9 +1435,9 @@ func TestShiftBookmark(t *testing.T) {
}
head := bookmarkedNode
bookmarkedNode.prev = nil
bookmarkedNode.next = originalBookmarkNext
tip.next = nil
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")

View File

@@ -15,11 +15,11 @@ var (
defaultAllowance = time.Second * 30
)
// node defines a linked list node for an orderbook item
type node struct {
value Item
next *node
prev *node
// Node defines a linked list node for an orderbook item
type Node struct {
Value Item
Next *Node
Prev *Node
// Denotes time pushed to stack, this will influence cleanup routine when
// there is a pause or minimal actions during period
shelved time.Time
@@ -27,7 +27,7 @@ type node struct {
// stack defines a FILO list of reusable nodes
type stack struct {
nodes []*node
nodes []*Node
sema uint32
count int32
}
@@ -51,7 +51,7 @@ func getNow() now {
// Push pushes a node pointer into the stack to be reused the time is passed in
// to allow for inlining which sets the time at which the node is theoretically
// pushed to a stack.
func (s *stack) Push(n *node, tn now) {
func (s *stack) Push(n *Node, tn now) {
if !atomic.CompareAndSwapUint32(&s.sema, neutral, active) {
// Stack is in use, for now we can dereference pointer
n = nil
@@ -59,9 +59,9 @@ func (s *stack) Push(n *node, tn now) {
}
// Adds a time when its placed back on to stack.
n.shelved = time.Time(tn)
n.next = nil
n.prev = nil
n.value = Item{}
n.Next = nil
n.Prev = nil
n.Value = Item{}
// Allows for resize when overflow TODO: rethink this
s.nodes = append(s.nodes[:s.count], n)
@@ -71,17 +71,17 @@ func (s *stack) Push(n *node, tn now) {
// Pop returns the last pointer off the stack and reduces the count and if empty
// will produce a lovely fresh node
func (s *stack) Pop() *node {
func (s *stack) Pop() *Node {
if !atomic.CompareAndSwapUint32(&s.sema, neutral, active) {
// Stack is in use, for now we can allocate a new node pointer
return &node{}
return &Node{}
}
if s.count == 0 {
// Create an empty node when no nodes are in slice or when cleaning
// service is running
atomic.StoreUint32(&s.sema, neutral)
return &node{}
return &Node{}
}
s.count--
n := s.nodes[s.count]

View File

@@ -9,7 +9,7 @@ import (
func TestPushPop(t *testing.T) {
s := newStack()
var nSlice [100]*node
var nSlice [100]*Node
for i := 0; i < 100; i++ {
nSlice[i] = s.Pop()
}
@@ -29,7 +29,7 @@ func TestPushPop(t *testing.T) {
func TestCleaner(t *testing.T) {
s := newStack()
var nSlice [100]*node
var nSlice [100]*Node
for i := 0; i < 100; i++ {
nSlice[i] = s.Pop()
}
@@ -64,20 +64,20 @@ func (s *stack) Display() {
// 158 9,521,717 ns/op 9600104 B/op 100001 allocs/op
func BenchmarkWithoutStack(b *testing.B) {
var n *node
var n *Node
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 100000; j++ {
n = new(node)
n.value.Price = 1337
n = new(Node)
n.Value.Price = 1337
}
}
}
// 316 3,485,211 ns/op 1 B/op 0 allocs/op
func BenchmarkWithStack(b *testing.B) {
var n *node
var n *Node
stack := newStack()
b.ReportAllocs()
b.ResetTimer()
@@ -85,7 +85,7 @@ func BenchmarkWithStack(b *testing.B) {
for i := 0; i < b.N; i++ {
for j := 0; j < 100000; j++ {
n = stack.Pop()
n.value.Price = 1337
n.Value.Price = 1337
stack.Push(n, tn)
}
}

View File

@@ -124,6 +124,9 @@ func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (*
book, ok := m3[p.Quote.Item]
if !ok {
book = newDepth(m1.ID)
book.exchange = exchange
book.pair = p
book.asset = a
m3[p.Quote.Item] = book
}
return book, nil

View File

@@ -0,0 +1,62 @@
package orderbook
import (
"sync"
"time"
)
// Unsafe is an exported linked list reference to the current bid/ask heads and
// a reference to the underlying depth mutex. This allows for the exposure of
// the internal list to an external strategy or subsystem. The bid and ask
// fields point to the actual head fields contained on both linked list structs,
// so that this struct can be reusable and not needed to be called on each
// inspection.
type Unsafe struct {
BidHead **Node
AskHead **Node
m *sync.Mutex
// UpdatedViaREST defines if sync manager is updating this book via the REST
// protocol then this book is not considered live and cannot be trusted.
UpdatedViaREST *bool
LastUpdated *time.Time
*Alert
}
// Lock locks down the underlying linked list which inhibits all pending updates
// for strategy inspection.
func (src *Unsafe) Lock() {
src.m.Lock()
}
// Unlock unlocks the underlying linked list after inspection by a strategy to
// resume normal operations
func (src *Unsafe) Unlock() {
src.m.Unlock()
}
// LockWith locks both books for the context of cross orderbook inspection.
// WARNING: When inspecting diametrically opposed books a higher order mutex
// MUST be used or a dead lock will occur.
func (src *Unsafe) LockWith(dst sync.Locker) {
src.m.Lock()
dst.Lock()
}
// UnlockWith unlocks both books for the context of cross orderbook inspection
func (src *Unsafe) UnlockWith(dst sync.Locker) {
dst.Unlock() // Unlock in reverse order
src.m.Unlock()
}
// GetUnsafe returns an unsafe orderbook with pointers to the linked list heads.
func (d *Depth) GetUnsafe() Unsafe {
return Unsafe{
BidHead: &d.bids.linkedList.head,
AskHead: &d.asks.linkedList.head,
m: &d.m,
Alert: &d.Alert,
UpdatedViaREST: &d.options.restSnapshot,
LastUpdated: &d.options.lastUpdated,
}
}

View File

@@ -0,0 +1,28 @@
package orderbook
import (
"testing"
"github.com/gofrs/uuid"
)
var unsafeID, _ = uuid.NewV4()
type externalBook struct{}
func (e *externalBook) Lock() {}
func (e *externalBook) Unlock() {}
func TestUnsafe(t *testing.T) {
d := newDepth(unsafeID)
ob := d.GetUnsafe()
if ob.AskHead == nil || ob.BidHead == nil || ob.m == nil {
t.Fatal("these items should not be nil")
}
ob2 := &externalBook{}
ob.Lock()
ob.Unlock() // nolint:go-staticcheck // Not needed in test
ob.LockWith(ob2)
ob.UnlockWith(ob2)
}