forked from thrasher-corp/gocryptotrader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
unsafe.go
168 lines (147 loc) · 4.69 KB
/
unsafe.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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
}