Skip to content

Commit

Permalink
orderbook: export NewDepth(), add methods to orderbook.Unsafe type (t…
Browse files Browse the repository at this point in the history
…hrasher-corp#907)

* orderbook: export NewDepth function, return unsafe pointer

* orderbook: Add unsafe methods and liquidity checks

* Update exchanges/orderbook/unsafe.go

Co-authored-by: Scott <[email protected]>

* orderbook: addr nits

* orderbook: update comments

* Update exchanges/orderbook/unsafe.go

Co-authored-by: Adrian Gallagher <[email protected]>

* Update exchanges/orderbook/unsafe.go

Co-authored-by: Adrian Gallagher <[email protected]>

Co-authored-by: Scott <[email protected]>
Co-authored-by: Adrian Gallagher <[email protected]>
  • Loading branch information
3 people authored Mar 25, 2022
1 parent c84bc86 commit 489e2eb
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 17 deletions.
2 changes: 1 addition & 1 deletion exchanges/orderbook/depth.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Depth struct {
}

// NewDepth returns a new depth item
func newDepth(id uuid.UUID) *Depth {
func NewDepth(id uuid.UUID) *Depth {
return &Depth{
stack: newStack(),
id: id,
Expand Down
22 changes: 11 additions & 11 deletions exchanges/orderbook/depth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
var id, _ = uuid.NewV4()

func TestGetLength(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
if d.GetAskLength() != 0 {
t.Errorf("expected len %v, but received %v", 0, d.GetAskLength())
}
Expand All @@ -25,7 +25,7 @@ func TestGetLength(t *testing.T) {
t.Errorf("expected len %v, but received %v", 1, d.GetAskLength())
}

d = newDepth(id)
d = NewDepth(id)
if d.GetBidLength() != 0 {
t.Errorf("expected len %v, but received %v", 0, d.GetBidLength())
}
Expand All @@ -38,7 +38,7 @@ func TestGetLength(t *testing.T) {
}

func TestRetrieve(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.asks.load([]Item{{Price: 1337}}, d.stack)
d.bids.load([]Item{{Price: 1337}}, d.stack)
d.options = options{
Expand Down Expand Up @@ -75,7 +75,7 @@ func TestRetrieve(t *testing.T) {
}

func TestTotalAmounts(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)

liquidity, value := d.TotalBidAmounts()
if liquidity != 0 || value != 0 {
Expand Down Expand Up @@ -118,15 +118,15 @@ func TestTotalAmounts(t *testing.T) {
}

func TestLoadSnapshot(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false)
if d.Retrieve().Asks[0].Price != 1337 || d.Retrieve().Bids[0].Price != 1337 {
t.Fatal("not set")
}
}

func TestFlush(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false)
d.Flush()
if len(d.Retrieve().Asks) != 0 || len(d.Retrieve().Bids) != 0 {
Expand All @@ -140,7 +140,7 @@ func TestFlush(t *testing.T) {
}

func TestUpdateBidAskByPrice(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

// empty
Expand All @@ -157,7 +157,7 @@ func TestUpdateBidAskByPrice(t *testing.T) {
}

func TestDeleteBidAskByID(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
err := d.DeleteBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}}, false, 0, time.Time{})
if err != nil {
Expand All @@ -184,7 +184,7 @@ func TestDeleteBidAskByID(t *testing.T) {
}

func TestUpdateBidAskByID(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
err := d.UpdateBidAskByID(Items{{Price: 1337, Amount: 2, ID: 1}}, Items{{Price: 1337, Amount: 2, ID: 2}}, 0, time.Time{})
if err != nil {
Expand All @@ -207,7 +207,7 @@ func TestUpdateBidAskByID(t *testing.T) {
}

func TestInsertBidAskByID(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
err := d.InsertBidAskByID(Items{{Price: 1338, Amount: 2, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}}, 0, time.Time{})
if err != nil {
Expand All @@ -219,7 +219,7 @@ func TestInsertBidAskByID(t *testing.T) {
}

func TestUpdateInsertByID(t *testing.T) {
d := newDepth(id)
d := NewDepth(id)
d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

err := d.UpdateInsertByID(Items{{Price: 1338, Amount: 0, ID: 3}}, Items{{Price: 1336, Amount: 2, ID: 4}}, 0, time.Time{})
Expand Down
4 changes: 2 additions & 2 deletions exchanges/orderbook/orderbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (s *Service) Update(b *Base) error {

book, ok := m3[b.Pair.Quote.Item]
if !ok {
book = newDepth(m1.ID)
book = NewDepth(m1.ID)
book.AssignOptions(b)
m3[b.Pair.Quote.Item] = book
}
Expand Down Expand Up @@ -122,7 +122,7 @@ 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 = NewDepth(m1.ID)
book.exchange = exchange
book.pair = p
book.asset = a
Expand Down
108 changes: 106 additions & 2 deletions exchanges/orderbook/unsafe.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package orderbook

import (
"errors"
"fmt"
"sync"
"time"

"github.com/thrasher-corp/gocryptotrader/exchanges/alert"
)

var errNoLiquidity = errors.New("no liquidity")

// 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
Expand Down Expand Up @@ -52,8 +56,8 @@ func (src *Unsafe) UnlockWith(dst sync.Locker) {
}

// GetUnsafe returns an unsafe orderbook with pointers to the linked list heads.
func (d *Depth) GetUnsafe() Unsafe {
return Unsafe{
func (d *Depth) GetUnsafe() *Unsafe {
return &Unsafe{
BidHead: &d.bids.linkedList.head,
AskHead: &d.asks.linkedList.head,
m: &d.m,
Expand All @@ -62,3 +66,103 @@ func (d *Depth) GetUnsafe() Unsafe {
LastUpdated: &d.options.lastUpdated,
}
}

// CheckBidLiquidity determines if the liquidity is sufficient for usage
func (src *Unsafe) CheckBidLiquidity() error {
_, err := src.GetBidLiquidity()
return err
}

// CheckAskLiquidity determines if the liquidity is sufficient for usage
func (src *Unsafe) CheckAskLiquidity() error {
_, err := src.GetAskLiquidity()
return err
}

// GetBestBid returns the top bid price
func (src *Unsafe) GetBestBid() (float64, error) {
bid, err := src.GetBidLiquidity()
if err != nil {
return 0, fmt.Errorf("get orderbook best bid price %w", err)
}
return bid.Value.Price, nil
}

// GetBestAsk returns the top ask price
func (src *Unsafe) GetBestAsk() (float64, error) {
ask, err := src.GetAskLiquidity()
if err != nil {
return 0, fmt.Errorf("get orderbook best ask price %w", err)
}
return ask.Value.Price, nil
}

// GetBidLiquidity gets the head node for the bid liquidity
func (src *Unsafe) GetBidLiquidity() (*Node, error) {
n := *src.BidHead
if n == nil {
return nil, fmt.Errorf("bid %w", errNoLiquidity)
}
return n, nil
}

// GetAskLiquidity gets the head node for the ask liquidity
func (src *Unsafe) GetAskLiquidity() (*Node, error) {
n := *src.AskHead
if n == nil {
return nil, fmt.Errorf("ask %w", errNoLiquidity)
}
return n, nil
}

// GetLiquidity checks and returns nodes to the top bids and asks
func (src *Unsafe) GetLiquidity() (ask, bid *Node, err error) {
bid, err = src.GetBidLiquidity()
if err != nil {
return nil, nil, err
}
ask, err = src.GetAskLiquidity()
if err != nil {
return nil, nil, err
}
return ask, bid, nil
}

// GetMidPrice returns the average between the top bid and top ask.
func (src *Unsafe) GetMidPrice() (float64, error) {
ask, bid, err := src.GetLiquidity()
if err != nil {
return 0, fmt.Errorf("get orderbook mid price %w", err)
}
return (bid.Value.Price + ask.Value.Price) / 2, nil
}

// GetSpread returns the spread between the top bid and top asks.
func (src *Unsafe) GetSpread() (float64, error) {
ask, bid, err := src.GetLiquidity()
if err != nil {
return 0, fmt.Errorf("get orderbook price spread %w", err)
}
return ask.Value.Price - bid.Value.Price, nil
}

// GetImbalance returns difference between the top bid and top ask amounts
// divided by its sum.
func (src *Unsafe) GetImbalance() (float64, error) {
ask, bid, err := src.GetLiquidity()
if err != nil {
return 0, fmt.Errorf("get orderbook imbalance %w", err)
}
top := bid.Value.Amount - ask.Value.Amount
bottom := bid.Value.Amount + ask.Value.Amount
if bottom == 0 {
return 0, errNoLiquidity
}
return top / bottom, nil
}

// IsStreaming returns if the orderbook is updated by a streaming protocol and
// is most likely more up to date than that of a REST protocol update.
func (src *Unsafe) IsStreaming() bool {
return !*src.UpdatedViaREST
}
Loading

0 comments on commit 489e2eb

Please sign in to comment.