mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* 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>
169 lines
4.7 KiB
Go
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
|
|
}
|