mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
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>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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{
|
||||
@@ -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 {
|
||||
@@ -118,7 +118,7 @@ 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")
|
||||
@@ -126,7 +126,7 @@ func TestLoadSnapshot(t *testing.T) {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -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{})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package orderbook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
@@ -14,7 +16,8 @@ func (e *externalBook) Lock() {}
|
||||
func (e *externalBook) Unlock() {}
|
||||
|
||||
func TestUnsafe(t *testing.T) {
|
||||
d := newDepth(unsafeID)
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
ob := d.GetUnsafe()
|
||||
if ob.AskHead == nil || ob.BidHead == nil || ob.m == nil {
|
||||
t.Fatal("these items should not be nil")
|
||||
@@ -26,3 +29,209 @@ func TestUnsafe(t *testing.T) {
|
||||
ob.LockWith(ob2)
|
||||
ob.UnlockWith(ob2)
|
||||
}
|
||||
|
||||
func TestGetLiquidity(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
_, _, err := unsafe.GetLiquidity()
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Time{}, false)
|
||||
_, _, err = unsafe.GetLiquidity()
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 2}}, []Item{{Price: 2}}, 0, time.Time{}, false)
|
||||
aN, bN, err := unsafe.GetLiquidity()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if aN == nil {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
if bN == nil {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckBidLiquidity(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
err := unsafe.CheckBidLiquidity()
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Time{}, false)
|
||||
err = unsafe.CheckBidLiquidity()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAskLiquidity(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
err := unsafe.CheckAskLiquidity()
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot(nil, []Item{{Price: 2}}, 0, time.Time{}, false)
|
||||
err = unsafe.CheckAskLiquidity()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBestBid(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
if _, err := unsafe.GetBestBid(); !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Time{}, false)
|
||||
bestBid, err := unsafe.GetBestBid()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if bestBid != 2 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBestAsk(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
if _, err := unsafe.GetBestAsk(); !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot(nil, []Item{{Price: 2}}, 0, time.Time{}, false)
|
||||
bestAsk, err := unsafe.GetBestAsk()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if bestAsk != 2 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMidPrice(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
if _, err := unsafe.GetMidPrice(); !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 1}}, []Item{{Price: 2}}, 0, time.Time{}, false)
|
||||
mid, err := unsafe.GetMidPrice()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if mid != 1.5 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSpread(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
if _, err := unsafe.GetSpread(); !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 1}}, []Item{{Price: 2}}, 0, time.Time{}, false)
|
||||
spread, err := unsafe.GetSpread()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if spread != 1 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImbalance(t *testing.T) {
|
||||
t.Parallel()
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
_, err := unsafe.GetImbalance()
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
// unlikely event zero amounts
|
||||
d.LoadSnapshot([]Item{{Price: 1, Amount: 0}}, []Item{{Price: 2, Amount: 0}}, 0, time.Time{}, false)
|
||||
_, err = unsafe.GetImbalance()
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
// balance skewed to asks
|
||||
d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1000}}, 0, time.Time{}, false)
|
||||
imbalance, err := unsafe.GetImbalance()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if imbalance != -0.998001998001998 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
// balance skewed to bids
|
||||
d.LoadSnapshot([]Item{{Price: 1, Amount: 1000}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, false)
|
||||
imbalance, err = unsafe.GetImbalance()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if imbalance != 0.998001998001998 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
// in balance
|
||||
d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, false)
|
||||
imbalance, err = unsafe.GetImbalance()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if imbalance != 0 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsStreaming(t *testing.T) {
|
||||
d := NewDepth(unsafeID)
|
||||
unsafe := d.GetUnsafe()
|
||||
if !unsafe.IsStreaming() {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", unsafe.IsStreaming(), true)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, true)
|
||||
if unsafe.IsStreaming() {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", unsafe.IsStreaming(), false)
|
||||
}
|
||||
|
||||
d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, false)
|
||||
if !unsafe.IsStreaming() {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", unsafe.IsStreaming(), true)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user