mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
fix preorder, postorder and inorder
This commit is contained in:
parent
09d68057c5
commit
56b2c8845b
|
@ -48,11 +48,11 @@ var rootCmd = &cobra.Command{
|
|||
stream.SetPublicOnly()
|
||||
stream.Subscribe(types.BookChannel, symbol, types.SubscribeOptions{})
|
||||
|
||||
stream.OnBookSnapshot(func(book types.OrderBook) {
|
||||
stream.OnBookSnapshot(func(book types.SliceOrderBook) {
|
||||
// log.Infof("book snapshot: %+v", book)
|
||||
})
|
||||
|
||||
stream.OnBookUpdate(func(book types.OrderBook) {
|
||||
stream.OnBookUpdate(func(book types.SliceOrderBook) {
|
||||
// log.Infof("book update: %+v", book)
|
||||
})
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ func (store *MarketDataStore) SetKLineWindows(windows map[types.Interval]types.K
|
|||
store.KLineWindows = windows
|
||||
}
|
||||
|
||||
func (store *MarketDataStore) OrderBook() types.OrderBook {
|
||||
func (store *MarketDataStore) OrderBook() types.SliceOrderBook {
|
||||
return store.orderBook.Copy()
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ func (store *MarketDataStore) KLinesOfInterval(interval types.Interval) (kLines
|
|||
return kLines, ok
|
||||
}
|
||||
|
||||
func (store *MarketDataStore) handleOrderBookUpdate(book types.OrderBook) {
|
||||
func (store *MarketDataStore) handleOrderBookUpdate(book types.SliceOrderBook) {
|
||||
if book.Symbol != store.Symbol {
|
||||
return
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func (store *MarketDataStore) handleOrderBookUpdate(book types.OrderBook) {
|
|||
store.EmitOrderBookUpdate(store.orderBook)
|
||||
}
|
||||
|
||||
func (store *MarketDataStore) handleOrderBookSnapshot(book types.OrderBook) {
|
||||
func (store *MarketDataStore) handleOrderBookSnapshot(book types.SliceOrderBook) {
|
||||
if book.Symbol != store.Symbol {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -49,10 +49,10 @@ var orderbookCmd = &cobra.Command{
|
|||
s := ex.NewStream()
|
||||
s.SetPublicOnly()
|
||||
s.Subscribe(types.BookChannel, symbol, types.SubscribeOptions{})
|
||||
s.OnBookSnapshot(func(book types.OrderBook) {
|
||||
s.OnBookSnapshot(func(book types.SliceOrderBook) {
|
||||
log.Infof("orderbook snapshot: %s", book.String())
|
||||
})
|
||||
s.OnBookUpdate(func(book types.OrderBook) {
|
||||
s.OnBookUpdate(func(book types.SliceOrderBook) {
|
||||
log.Infof("orderbook update: %s", book.String())
|
||||
})
|
||||
|
||||
|
|
|
@ -319,7 +319,7 @@ type DepthEvent struct {
|
|||
Asks []DepthEntry
|
||||
}
|
||||
|
||||
func (e *DepthEvent) OrderBook() (book types.OrderBook, err error) {
|
||||
func (e *DepthEvent) OrderBook() (book types.SliceOrderBook, err error) {
|
||||
book.Symbol = e.Symbol
|
||||
|
||||
for _, entry := range e.Bids {
|
||||
|
|
|
@ -351,16 +351,16 @@ func checksumString(bids, asks [][]json.Number) string {
|
|||
|
||||
var errUnmatchedChecksum = fmt.Errorf("unmatched checksum")
|
||||
|
||||
func toGlobalOrderBook(r orderBookResponse) (types.OrderBook, error) {
|
||||
func toGlobalOrderBook(r orderBookResponse) (types.SliceOrderBook, error) {
|
||||
bids, err := toPriceVolumeSlice(r.Bids)
|
||||
if err != nil {
|
||||
return types.OrderBook{}, fmt.Errorf("can't convert bids to priceVolumeSlice: %w", err)
|
||||
return types.SliceOrderBook{}, fmt.Errorf("can't convert bids to priceVolumeSlice: %w", err)
|
||||
}
|
||||
asks, err := toPriceVolumeSlice(r.Asks)
|
||||
if err != nil {
|
||||
return types.OrderBook{}, fmt.Errorf("can't convert asks to priceVolumeSlice: %w", err)
|
||||
return types.SliceOrderBook{}, fmt.Errorf("can't convert asks to priceVolumeSlice: %w", err)
|
||||
}
|
||||
return types.OrderBook{
|
||||
return types.SliceOrderBook{
|
||||
// ex. BTC/USDT
|
||||
Symbol: toGlobalSymbol(strings.ToUpper(r.Market)),
|
||||
Bids: bids,
|
||||
|
|
|
@ -174,7 +174,7 @@ func (e *BookEvent) Time() time.Time {
|
|||
return time.Unix(0, e.Timestamp*int64(time.Millisecond))
|
||||
}
|
||||
|
||||
func (e *BookEvent) OrderBook() (snapshot types.OrderBook, err error) {
|
||||
func (e *BookEvent) OrderBook() (snapshot types.SliceOrderBook, err error) {
|
||||
snapshot.Symbol = strings.ToUpper(e.Market)
|
||||
|
||||
for _, bid := range e.Bids {
|
||||
|
|
|
@ -2,11 +2,8 @@ package types
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/sigchan"
|
||||
)
|
||||
|
@ -20,236 +17,55 @@ func (p PriceVolume) String() string {
|
|||
return fmt.Sprintf("PriceVolume{ price: %f, volume: %f }", p.Price.Float64(), p.Volume.Float64())
|
||||
}
|
||||
|
||||
//go:generate callbackgen -type RBOrderBook
|
||||
type RBOrderBook struct {
|
||||
Symbol string
|
||||
Bids *RBTree
|
||||
Asks *RBTree
|
||||
|
||||
loadCallbacks []func(book *RBOrderBook)
|
||||
updateCallbacks []func(book *RBOrderBook)
|
||||
}
|
||||
|
||||
func NewRBOrderBook(symbol string) *RBOrderBook {
|
||||
return &RBOrderBook{
|
||||
Symbol: symbol,
|
||||
Bids: NewRBTree(),
|
||||
Asks: NewRBTree(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *RBOrderBook) BestBid() (PriceVolume, bool) {
|
||||
return PriceVolume{}, true
|
||||
}
|
||||
|
||||
func (b *RBOrderBook) BestAsk() (PriceVolume, bool) {
|
||||
return PriceVolume{}, true
|
||||
}
|
||||
|
||||
//go:generate callbackgen -type OrderBook
|
||||
type OrderBook struct {
|
||||
Symbol string
|
||||
Bids PriceVolumeSlice
|
||||
Asks PriceVolumeSlice
|
||||
|
||||
loadCallbacks []func(book *OrderBook)
|
||||
updateCallbacks []func(book *OrderBook)
|
||||
}
|
||||
|
||||
func (b *OrderBook) Spread() (fixedpoint.Value, bool) {
|
||||
bestBid, ok := b.BestBid()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
bestAsk, ok := b.BestAsk()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return bestAsk.Price - bestBid.Price, true
|
||||
}
|
||||
|
||||
func (b *OrderBook) BestBid() (PriceVolume, bool) {
|
||||
if len(b.Bids) == 0 {
|
||||
return PriceVolume{}, false
|
||||
}
|
||||
|
||||
return b.Bids[0], true
|
||||
}
|
||||
|
||||
func (b *OrderBook) BestAsk() (PriceVolume, bool) {
|
||||
if len(b.Asks) == 0 {
|
||||
return PriceVolume{}, false
|
||||
}
|
||||
|
||||
return b.Asks[0], true
|
||||
}
|
||||
|
||||
func (b *OrderBook) IsValid() (bool, error) {
|
||||
bid, hasBid := b.BestBid()
|
||||
ask, hasAsk := b.BestAsk()
|
||||
|
||||
if !hasBid {
|
||||
return false, errors.New("empty bids")
|
||||
}
|
||||
|
||||
if !hasAsk {
|
||||
return false, errors.New("empty asks")
|
||||
}
|
||||
|
||||
if bid.Price > ask.Price {
|
||||
return false, fmt.Errorf("bid price %f > ask price %f", bid.Price.Float64(), ask.Price.Float64())
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (b *OrderBook) PriceVolumesBySide(side SideType) PriceVolumeSlice {
|
||||
switch side {
|
||||
|
||||
case SideTypeBuy:
|
||||
return b.Bids
|
||||
|
||||
case SideTypeSell:
|
||||
return b.Asks
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *OrderBook) CopyDepth(depth int) (book OrderBook) {
|
||||
book = *b
|
||||
book.Bids = book.Bids.CopyDepth(depth)
|
||||
book.Asks = book.Asks.CopyDepth(depth)
|
||||
return book
|
||||
}
|
||||
|
||||
func (b *OrderBook) Copy() (book OrderBook) {
|
||||
book = *b
|
||||
book.Bids = book.Bids.Copy()
|
||||
book.Asks = book.Asks.Copy()
|
||||
return book
|
||||
}
|
||||
|
||||
func (b *OrderBook) updateAsks(pvs PriceVolumeSlice) {
|
||||
for _, pv := range pvs {
|
||||
if pv.Volume == 0 {
|
||||
b.Asks = b.Asks.Remove(pv.Price, false)
|
||||
} else {
|
||||
b.Asks = b.Asks.Upsert(pv, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *OrderBook) updateBids(pvs PriceVolumeSlice) {
|
||||
for _, pv := range pvs {
|
||||
if pv.Volume == 0 {
|
||||
b.Bids = b.Bids.Remove(pv.Price, true)
|
||||
} else {
|
||||
b.Bids = b.Bids.Upsert(pv, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *OrderBook) update(book OrderBook) {
|
||||
b.updateBids(book.Bids)
|
||||
b.updateAsks(book.Asks)
|
||||
}
|
||||
|
||||
func (b *OrderBook) Reset() {
|
||||
b.Bids = nil
|
||||
b.Asks = nil
|
||||
}
|
||||
|
||||
func (b *OrderBook) Load(book OrderBook) {
|
||||
b.Reset()
|
||||
b.update(book)
|
||||
b.EmitLoad(b)
|
||||
}
|
||||
|
||||
func (b *OrderBook) Update(book OrderBook) {
|
||||
b.update(book)
|
||||
b.EmitUpdate(b)
|
||||
}
|
||||
|
||||
func (b *OrderBook) Print() {
|
||||
fmt.Printf(b.String())
|
||||
}
|
||||
|
||||
func (b *OrderBook) String() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
sb.WriteString("BOOK ")
|
||||
sb.WriteString(b.Symbol)
|
||||
sb.WriteString("\n")
|
||||
|
||||
if len(b.Asks) > 0 {
|
||||
sb.WriteString("ASKS:\n")
|
||||
for i := len(b.Asks) - 1; i >= 0; i-- {
|
||||
sb.WriteString("- ASK: ")
|
||||
sb.WriteString(b.Asks[i].String())
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
if len(b.Bids) > 0 {
|
||||
sb.WriteString("BIDS:\n")
|
||||
for _, bid := range b.Bids {
|
||||
sb.WriteString("- BID: ")
|
||||
sb.WriteString(bid.String())
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
type OrderBook interface {
|
||||
Spread() (fixedpoint.Value, bool)
|
||||
BestAsk() (PriceVolume, bool)
|
||||
BestBid() (PriceVolume, bool)
|
||||
Reset()
|
||||
Load(book SliceOrderBook)
|
||||
Update(book SliceOrderBook)
|
||||
}
|
||||
|
||||
type MutexOrderBook struct {
|
||||
sync.Mutex
|
||||
|
||||
*OrderBook
|
||||
*SliceOrderBook
|
||||
}
|
||||
|
||||
func NewMutexOrderBook(symbol string) *MutexOrderBook {
|
||||
return &MutexOrderBook{
|
||||
OrderBook: &OrderBook{Symbol: symbol},
|
||||
SliceOrderBook: &SliceOrderBook{Symbol: symbol},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *MutexOrderBook) Load(book OrderBook) {
|
||||
func (b *MutexOrderBook) Load(book SliceOrderBook) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.OrderBook.Reset()
|
||||
b.OrderBook.update(book)
|
||||
b.EmitLoad(b.OrderBook)
|
||||
b.SliceOrderBook.Load(book)
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
func (b *MutexOrderBook) Reset() {
|
||||
b.Lock()
|
||||
b.OrderBook.Reset()
|
||||
b.SliceOrderBook.Reset()
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
func (b *MutexOrderBook) CopyDepth(depth int) OrderBook {
|
||||
func (b *MutexOrderBook) CopyDepth(depth int) SliceOrderBook {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
return b.OrderBook.CopyDepth(depth)
|
||||
return b.SliceOrderBook.CopyDepth(depth)
|
||||
}
|
||||
|
||||
func (b *MutexOrderBook) Get() OrderBook {
|
||||
func (b *MutexOrderBook) Get() SliceOrderBook {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
return b.OrderBook.Copy()
|
||||
return b.SliceOrderBook.Copy()
|
||||
}
|
||||
|
||||
func (b *MutexOrderBook) Update(update OrderBook) {
|
||||
func (b *MutexOrderBook) Update(update SliceOrderBook) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
b.OrderBook.update(update)
|
||||
b.EmitUpdate(b.OrderBook)
|
||||
b.SliceOrderBook.Update(update)
|
||||
b.Unlock()
|
||||
}
|
||||
|
||||
// StreamOrderBook receives streaming data from websocket connection and
|
||||
|
@ -268,7 +84,7 @@ func NewStreamBook(symbol string) *StreamOrderBook {
|
|||
}
|
||||
|
||||
func (sb *StreamOrderBook) BindStream(stream Stream) {
|
||||
stream.OnBookSnapshot(func(book OrderBook) {
|
||||
stream.OnBookSnapshot(func(book SliceOrderBook) {
|
||||
if sb.Symbol != book.Symbol {
|
||||
return
|
||||
}
|
||||
|
@ -277,7 +93,7 @@ func (sb *StreamOrderBook) BindStream(stream Stream) {
|
|||
sb.C.Emit()
|
||||
})
|
||||
|
||||
stream.OnBookUpdate(func(book OrderBook) {
|
||||
stream.OnBookUpdate(func(book SliceOrderBook) {
|
||||
if sb.Symbol != book.Symbol {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// Code generated by "callbackgen -type OrderBook"; DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import ()
|
||||
|
||||
func (b *OrderBook) OnLoad(cb func(book *OrderBook)) {
|
||||
b.loadCallbacks = append(b.loadCallbacks, cb)
|
||||
}
|
||||
|
||||
func (b *OrderBook) EmitLoad(book *OrderBook) {
|
||||
for _, cb := range b.loadCallbacks {
|
||||
cb(book)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *OrderBook) OnUpdate(cb func(book *OrderBook)) {
|
||||
b.updateCallbacks = append(b.updateCallbacks, cb)
|
||||
}
|
||||
|
||||
func (b *OrderBook) EmitUpdate(book *OrderBook) {
|
||||
for _, cb := range b.updateCallbacks {
|
||||
cb(book)
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ func BenchmarkOrderBook_Load(b *testing.B) {
|
|||
bids = append(bids, PriceVolume{fixedpoint.NewFromFloat(1000 - 0.1 - p), fixedpoint.NewFromFloat(1)})
|
||||
}
|
||||
|
||||
b.Run("RBOrderBook", func(b *testing.B) {
|
||||
b.Run("RBTOrderBook", func(b *testing.B) {
|
||||
book := NewRBOrderBook("ETHUSDT")
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, ask := range asks {
|
||||
|
@ -36,8 +36,8 @@ func BenchmarkOrderBook_Load(b *testing.B) {
|
|||
}
|
||||
})
|
||||
|
||||
b.Run("OrderBook", func(b *testing.B) {
|
||||
book := &OrderBook{}
|
||||
b.Run("SliceOrderBook", func(b *testing.B) {
|
||||
book := &SliceOrderBook{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, ask := range asks {
|
||||
book.Asks = book.Asks.Upsert(ask, false)
|
||||
|
@ -64,7 +64,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
|
|||
rbBook.Bids.Upsert(bid.Price, bid.Volume)
|
||||
}
|
||||
|
||||
b.Run("RBOrderBook", func(b *testing.B) {
|
||||
b.Run("RBTOrderBook", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
|
||||
if price >= fixedpoint.NewFromFloat(1000) {
|
||||
|
@ -75,7 +75,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
|
|||
}
|
||||
})
|
||||
|
||||
sliceBook := &OrderBook{}
|
||||
sliceBook := &SliceOrderBook{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, ask := range asks {
|
||||
sliceBook.Asks = sliceBook.Asks.Upsert(ask, false)
|
||||
|
@ -84,7 +84,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
|
|||
sliceBook.Bids = sliceBook.Bids.Upsert(bid, true)
|
||||
}
|
||||
}
|
||||
b.Run("OrderBook", func(b *testing.B) {
|
||||
b.Run("SliceOrderBook", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
|
||||
if price >= fixedpoint.NewFromFloat(1000) {
|
||||
|
@ -97,7 +97,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
|
|||
}
|
||||
|
||||
func TestOrderBook_IsValid(t *testing.T) {
|
||||
ob := OrderBook{
|
||||
ob := SliceOrderBook{
|
||||
Bids: PriceVolumeSlice{
|
||||
{fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(1.5)},
|
||||
{fixedpoint.NewFromFloat(90.0), fixedpoint.NewFromFloat(2.5)},
|
||||
|
|
132
pkg/types/rbtorderbook.go
Normal file
132
pkg/types/rbtorderbook.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
//go:generate callbackgen -type RBTOrderBook
|
||||
type RBTOrderBook struct {
|
||||
Symbol string
|
||||
Bids *RBTree
|
||||
Asks *RBTree
|
||||
|
||||
loadCallbacks []func(book *RBTOrderBook)
|
||||
updateCallbacks []func(book *RBTOrderBook)
|
||||
}
|
||||
|
||||
func NewRBOrderBook(symbol string) *RBTOrderBook {
|
||||
return &RBTOrderBook{
|
||||
Symbol: symbol,
|
||||
Bids: NewRBTree(),
|
||||
Asks: NewRBTree(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) BestBid() (PriceVolume, bool) {
|
||||
right := b.Bids.Rightmost(b.Bids.Root)
|
||||
if right != nil {
|
||||
return PriceVolume{Price: right.Key, Volume: right.Value}, true
|
||||
}
|
||||
return PriceVolume{}, false
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) BestAsk() (PriceVolume, bool) {
|
||||
left := b.Asks.Leftmost(b.Bids.Root)
|
||||
if left != nil {
|
||||
return PriceVolume{Price: left.Key, Volume: left.Value}, true
|
||||
}
|
||||
return PriceVolume{}, false
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) Spread() (fixedpoint.Value, bool) {
|
||||
bestBid, ok := b.BestBid()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
bestAsk, ok := b.BestAsk()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return bestAsk.Price - bestBid.Price, true
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) IsValid() (bool, error) {
|
||||
bid, hasBid := b.BestBid()
|
||||
ask, hasAsk := b.BestAsk()
|
||||
|
||||
if !hasBid {
|
||||
return false, errors.New("empty bids")
|
||||
}
|
||||
|
||||
if !hasAsk {
|
||||
return false, errors.New("empty asks")
|
||||
}
|
||||
|
||||
if bid.Price > ask.Price {
|
||||
return false, fmt.Errorf("bid price %f > ask price %f", bid.Price.Float64(), ask.Price.Float64())
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) Load(book SliceOrderBook) {
|
||||
b.Reset()
|
||||
b.update(book)
|
||||
b.EmitLoad(b)
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) Update(book SliceOrderBook) {
|
||||
b.update(book)
|
||||
b.EmitUpdate(b)
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) Reset() {
|
||||
b.Bids = NewRBTree()
|
||||
b.Asks = NewRBTree()
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) updateAsks(pvs PriceVolumeSlice) {
|
||||
for _, pv := range pvs {
|
||||
if pv.Volume == 0 {
|
||||
b.Asks.Delete(pv.Price)
|
||||
} else {
|
||||
b.Asks.Upsert(pv.Price, pv.Volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) updateBids(pvs PriceVolumeSlice) {
|
||||
for _, pv := range pvs {
|
||||
if pv.Volume == 0 {
|
||||
b.Bids.Delete(pv.Price)
|
||||
} else {
|
||||
b.Bids.Upsert(pv.Price, pv.Volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) update(book SliceOrderBook) {
|
||||
b.updateBids(book.Bids)
|
||||
b.updateAsks(book.Asks)
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) load(book SliceOrderBook) {
|
||||
b.Reset()
|
||||
b.updateBids(book.Bids)
|
||||
b.updateAsks(book.Asks)
|
||||
}
|
||||
|
||||
func (b *RBTOrderBook) Print() {
|
||||
b.Bids.PostorderOf(b.Bids.Root, func(n *RBNode) {
|
||||
fmt.Printf("bid: %f x %f", n.Key.Float64(), n.Value.Float64())
|
||||
})
|
||||
|
||||
b.Asks.PostorderOf(b.Asks.Root, func(n *RBNode) {
|
||||
fmt.Printf("ask: %f x %f", n.Key.Float64(), n.Value.Float64())
|
||||
})
|
||||
}
|
|
@ -341,26 +341,38 @@ func (tree *RBTree) Successor(current *RBNode) *RBNode {
|
|||
return newNode
|
||||
}
|
||||
|
||||
func (tree *RBTree) Preorder(current *RBNode, cb func(n *RBNode)) {
|
||||
func (tree *RBTree) Preorder(cb func(n *RBNode)) {
|
||||
tree.PreorderOf(tree.Root, cb)
|
||||
}
|
||||
|
||||
func (tree *RBTree) PreorderOf(current *RBNode, cb func(n *RBNode)) {
|
||||
if current != nil {
|
||||
cb(current)
|
||||
tree.Preorder(current.Left, cb)
|
||||
tree.Preorder(current.Right, cb)
|
||||
tree.PreorderOf(current.Left, cb)
|
||||
tree.PreorderOf(current.Right, cb)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RBTree) Inorder(current *RBNode, cb func(n *RBNode)) {
|
||||
func (tree *RBTree) Inorder(cb func(n *RBNode)) {
|
||||
tree.InorderOf(tree.Root, cb)
|
||||
}
|
||||
|
||||
func (tree *RBTree) InorderOf(current *RBNode, cb func(n *RBNode)) {
|
||||
if current != nil {
|
||||
tree.Preorder(current.Left, cb)
|
||||
tree.InorderOf(current.Left, cb)
|
||||
cb(current)
|
||||
tree.Preorder(current.Right, cb)
|
||||
tree.InorderOf(current.Right, cb)
|
||||
}
|
||||
}
|
||||
|
||||
func (tree *RBTree) Postorder(current *RBNode, cb func(n *RBNode)) {
|
||||
func (tree *RBTree) Postorder(cb func(n *RBNode)) {
|
||||
tree.PostorderOf(tree.Root, cb)
|
||||
}
|
||||
|
||||
func (tree *RBTree) PostorderOf(current *RBNode, cb func(n *RBNode)) {
|
||||
if current != nil {
|
||||
tree.Preorder(current.Left, cb)
|
||||
tree.Preorder(current.Right, cb)
|
||||
tree.PostorderOf(current.Left, cb)
|
||||
tree.PostorderOf(current.Right, cb)
|
||||
cb(current)
|
||||
}
|
||||
}
|
||||
|
|
174
pkg/types/sliceorderbook.go
Normal file
174
pkg/types/sliceorderbook.go
Normal file
|
@ -0,0 +1,174 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SliceOrderBook is a general order book structure which could be used
|
||||
// for RESTful responses and websocket stream parsing
|
||||
//go:generate callbackgen -type SliceOrderBook
|
||||
type SliceOrderBook struct {
|
||||
Symbol string
|
||||
Bids PriceVolumeSlice
|
||||
Asks PriceVolumeSlice
|
||||
|
||||
loadCallbacks []func(book *SliceOrderBook)
|
||||
updateCallbacks []func(book *SliceOrderBook)
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) Spread() (fixedpoint.Value, bool) {
|
||||
bestBid, ok := b.BestBid()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
bestAsk, ok := b.BestAsk()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return bestAsk.Price - bestBid.Price, true
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) BestBid() (PriceVolume, bool) {
|
||||
if len(b.Bids) == 0 {
|
||||
return PriceVolume{}, false
|
||||
}
|
||||
|
||||
return b.Bids[0], true
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) BestAsk() (PriceVolume, bool) {
|
||||
if len(b.Asks) == 0 {
|
||||
return PriceVolume{}, false
|
||||
}
|
||||
|
||||
return b.Asks[0], true
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) IsValid() (bool, error) {
|
||||
bid, hasBid := b.BestBid()
|
||||
ask, hasAsk := b.BestAsk()
|
||||
|
||||
if !hasBid {
|
||||
return false, errors.New("empty bids")
|
||||
}
|
||||
|
||||
if !hasAsk {
|
||||
return false, errors.New("empty asks")
|
||||
}
|
||||
|
||||
if bid.Price > ask.Price {
|
||||
return false, fmt.Errorf("bid price %f > ask price %f", bid.Price.Float64(), ask.Price.Float64())
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) PriceVolumesBySide(side SideType) PriceVolumeSlice {
|
||||
switch side {
|
||||
|
||||
case SideTypeBuy:
|
||||
return b.Bids
|
||||
|
||||
case SideTypeSell:
|
||||
return b.Asks
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) CopyDepth(depth int) (book SliceOrderBook) {
|
||||
book = *b
|
||||
book.Bids = book.Bids.CopyDepth(depth)
|
||||
book.Asks = book.Asks.CopyDepth(depth)
|
||||
return book
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) Copy() (book SliceOrderBook) {
|
||||
book = *b
|
||||
book.Bids = book.Bids.Copy()
|
||||
book.Asks = book.Asks.Copy()
|
||||
return book
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) updateAsks(pvs PriceVolumeSlice) {
|
||||
for _, pv := range pvs {
|
||||
if pv.Volume == 0 {
|
||||
b.Asks = b.Asks.Remove(pv.Price, false)
|
||||
} else {
|
||||
b.Asks = b.Asks.Upsert(pv, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) updateBids(pvs PriceVolumeSlice) {
|
||||
for _, pv := range pvs {
|
||||
if pv.Volume == 0 {
|
||||
b.Bids = b.Bids.Remove(pv.Price, true)
|
||||
} else {
|
||||
b.Bids = b.Bids.Upsert(pv, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) load(book SliceOrderBook) {
|
||||
b.Reset()
|
||||
b.update(book)
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) update(book SliceOrderBook) {
|
||||
b.updateBids(book.Bids)
|
||||
b.updateAsks(book.Asks)
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) Reset() {
|
||||
b.Bids = nil
|
||||
b.Asks = nil
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) Load(book SliceOrderBook) {
|
||||
b.load(book)
|
||||
b.EmitLoad(b)
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) Update(book SliceOrderBook) {
|
||||
b.update(book)
|
||||
b.EmitUpdate(b)
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) Print() {
|
||||
fmt.Printf(b.String())
|
||||
}
|
||||
|
||||
func (b *SliceOrderBook) String() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
sb.WriteString("BOOK ")
|
||||
sb.WriteString(b.Symbol)
|
||||
sb.WriteString("\n")
|
||||
|
||||
if len(b.Asks) > 0 {
|
||||
sb.WriteString("ASKS:\n")
|
||||
for i := len(b.Asks) - 1; i >= 0; i-- {
|
||||
sb.WriteString("- ASK: ")
|
||||
sb.WriteString(b.Asks[i].String())
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
if len(b.Bids) > 0 {
|
||||
sb.WriteString("BIDS:\n")
|
||||
for _, bid := range b.Bids {
|
||||
sb.WriteString("- BID: ")
|
||||
sb.WriteString(bid.String())
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
|
@ -94,21 +94,21 @@ func (stream *StandardStream) EmitKLine(kline KLine) {
|
|||
}
|
||||
}
|
||||
|
||||
func (stream *StandardStream) OnBookUpdate(cb func(book OrderBook)) {
|
||||
func (stream *StandardStream) OnBookUpdate(cb func(book SliceOrderBook)) {
|
||||
stream.bookUpdateCallbacks = append(stream.bookUpdateCallbacks, cb)
|
||||
}
|
||||
|
||||
func (stream *StandardStream) EmitBookUpdate(book OrderBook) {
|
||||
func (stream *StandardStream) EmitBookUpdate(book SliceOrderBook) {
|
||||
for _, cb := range stream.bookUpdateCallbacks {
|
||||
cb(book)
|
||||
}
|
||||
}
|
||||
|
||||
func (stream *StandardStream) OnBookSnapshot(cb func(book OrderBook)) {
|
||||
func (stream *StandardStream) OnBookSnapshot(cb func(book SliceOrderBook)) {
|
||||
stream.bookSnapshotCallbacks = append(stream.bookSnapshotCallbacks, cb)
|
||||
}
|
||||
|
||||
func (stream *StandardStream) EmitBookSnapshot(book OrderBook) {
|
||||
func (stream *StandardStream) EmitBookSnapshot(book SliceOrderBook) {
|
||||
for _, cb := range stream.bookSnapshotCallbacks {
|
||||
cb(book)
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ type StandardStreamEventHub interface {
|
|||
|
||||
OnKLine(cb func(kline KLine))
|
||||
|
||||
OnBookUpdate(cb func(book OrderBook))
|
||||
OnBookUpdate(cb func(book SliceOrderBook))
|
||||
|
||||
OnBookSnapshot(cb func(book OrderBook))
|
||||
OnBookSnapshot(cb func(book SliceOrderBook))
|
||||
}
|
||||
|
|
|
@ -44,9 +44,9 @@ type StandardStream struct {
|
|||
|
||||
kLineCallbacks []func(kline KLine)
|
||||
|
||||
bookUpdateCallbacks []func(book OrderBook)
|
||||
bookUpdateCallbacks []func(book SliceOrderBook)
|
||||
|
||||
bookSnapshotCallbacks []func(book OrderBook)
|
||||
bookSnapshotCallbacks []func(book SliceOrderBook)
|
||||
}
|
||||
|
||||
func (stream *StandardStream) Subscribe(channel Channel, symbol string, options SubscribeOptions) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user