Files
gocryptotrader/exchanges/orderbook/unsafe.go
Ryan O'Hara-Reid 489e2ebade orderbook: export NewDepth(), add methods to orderbook.Unsafe type (#907)
* orderbook: export NewDepth function, return unsafe pointer

* orderbook: Add unsafe methods and liquidity checks

* Update exchanges/orderbook/unsafe.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* orderbook: addr nits

* orderbook: update comments

* Update exchanges/orderbook/unsafe.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/orderbook/unsafe.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
2022-03-25 11:38:56 +11:00

169 lines
4.7 KiB
Go

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
// 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.Notice
}
// 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,
Notice: &d.Notice,
UpdatedViaREST: &d.options.restSnapshot,
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
}