mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +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.SetPublicOnly()
|
||||||
stream.Subscribe(types.BookChannel, symbol, types.SubscribeOptions{})
|
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)
|
// log.Infof("book snapshot: %+v", book)
|
||||||
})
|
})
|
||||||
|
|
||||||
stream.OnBookUpdate(func(book types.OrderBook) {
|
stream.OnBookUpdate(func(book types.SliceOrderBook) {
|
||||||
// log.Infof("book update: %+v", book)
|
// log.Infof("book update: %+v", book)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (store *MarketDataStore) SetKLineWindows(windows map[types.Interval]types.K
|
||||||
store.KLineWindows = windows
|
store.KLineWindows = windows
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MarketDataStore) OrderBook() types.OrderBook {
|
func (store *MarketDataStore) OrderBook() types.SliceOrderBook {
|
||||||
return store.orderBook.Copy()
|
return store.orderBook.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ func (store *MarketDataStore) KLinesOfInterval(interval types.Interval) (kLines
|
||||||
return kLines, ok
|
return kLines, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MarketDataStore) handleOrderBookUpdate(book types.OrderBook) {
|
func (store *MarketDataStore) handleOrderBookUpdate(book types.SliceOrderBook) {
|
||||||
if book.Symbol != store.Symbol {
|
if book.Symbol != store.Symbol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ func (store *MarketDataStore) handleOrderBookUpdate(book types.OrderBook) {
|
||||||
store.EmitOrderBookUpdate(store.orderBook)
|
store.EmitOrderBookUpdate(store.orderBook)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *MarketDataStore) handleOrderBookSnapshot(book types.OrderBook) {
|
func (store *MarketDataStore) handleOrderBookSnapshot(book types.SliceOrderBook) {
|
||||||
if book.Symbol != store.Symbol {
|
if book.Symbol != store.Symbol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,10 @@ var orderbookCmd = &cobra.Command{
|
||||||
s := ex.NewStream()
|
s := ex.NewStream()
|
||||||
s.SetPublicOnly()
|
s.SetPublicOnly()
|
||||||
s.Subscribe(types.BookChannel, symbol, types.SubscribeOptions{})
|
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())
|
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())
|
log.Infof("orderbook update: %s", book.String())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -319,7 +319,7 @@ type DepthEvent struct {
|
||||||
Asks []DepthEntry
|
Asks []DepthEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DepthEvent) OrderBook() (book types.OrderBook, err error) {
|
func (e *DepthEvent) OrderBook() (book types.SliceOrderBook, err error) {
|
||||||
book.Symbol = e.Symbol
|
book.Symbol = e.Symbol
|
||||||
|
|
||||||
for _, entry := range e.Bids {
|
for _, entry := range e.Bids {
|
||||||
|
|
|
@ -351,16 +351,16 @@ func checksumString(bids, asks [][]json.Number) string {
|
||||||
|
|
||||||
var errUnmatchedChecksum = fmt.Errorf("unmatched checksum")
|
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)
|
bids, err := toPriceVolumeSlice(r.Bids)
|
||||||
if err != nil {
|
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)
|
asks, err := toPriceVolumeSlice(r.Asks)
|
||||||
if err != nil {
|
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
|
// ex. BTC/USDT
|
||||||
Symbol: toGlobalSymbol(strings.ToUpper(r.Market)),
|
Symbol: toGlobalSymbol(strings.ToUpper(r.Market)),
|
||||||
Bids: bids,
|
Bids: bids,
|
||||||
|
|
|
@ -174,7 +174,7 @@ func (e *BookEvent) Time() time.Time {
|
||||||
return time.Unix(0, e.Timestamp*int64(time.Millisecond))
|
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)
|
snapshot.Symbol = strings.ToUpper(e.Market)
|
||||||
|
|
||||||
for _, bid := range e.Bids {
|
for _, bid := range e.Bids {
|
||||||
|
|
|
@ -2,11 +2,8 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/sigchan"
|
"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())
|
return fmt.Sprintf("PriceVolume{ price: %f, volume: %f }", p.Price.Float64(), p.Volume.Float64())
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate callbackgen -type RBOrderBook
|
type OrderBook interface {
|
||||||
type RBOrderBook struct {
|
Spread() (fixedpoint.Value, bool)
|
||||||
Symbol string
|
BestAsk() (PriceVolume, bool)
|
||||||
Bids *RBTree
|
BestBid() (PriceVolume, bool)
|
||||||
Asks *RBTree
|
Reset()
|
||||||
|
Load(book SliceOrderBook)
|
||||||
loadCallbacks []func(book *RBOrderBook)
|
Update(book SliceOrderBook)
|
||||||
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 MutexOrderBook struct {
|
type MutexOrderBook struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
*OrderBook
|
*SliceOrderBook
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMutexOrderBook(symbol string) *MutexOrderBook {
|
func NewMutexOrderBook(symbol string) *MutexOrderBook {
|
||||||
return &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()
|
b.Lock()
|
||||||
defer b.Unlock()
|
b.SliceOrderBook.Load(book)
|
||||||
|
b.Unlock()
|
||||||
b.OrderBook.Reset()
|
|
||||||
b.OrderBook.update(book)
|
|
||||||
b.EmitLoad(b.OrderBook)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *MutexOrderBook) Reset() {
|
func (b *MutexOrderBook) Reset() {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
b.OrderBook.Reset()
|
b.SliceOrderBook.Reset()
|
||||||
b.Unlock()
|
b.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *MutexOrderBook) CopyDepth(depth int) OrderBook {
|
func (b *MutexOrderBook) CopyDepth(depth int) SliceOrderBook {
|
||||||
b.Lock()
|
b.Lock()
|
||||||
defer b.Unlock()
|
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()
|
b.Lock()
|
||||||
defer b.Unlock()
|
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()
|
b.Lock()
|
||||||
defer b.Unlock()
|
b.SliceOrderBook.Update(update)
|
||||||
|
b.Unlock()
|
||||||
b.OrderBook.update(update)
|
|
||||||
b.EmitUpdate(b.OrderBook)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamOrderBook receives streaming data from websocket connection and
|
// StreamOrderBook receives streaming data from websocket connection and
|
||||||
|
@ -268,7 +84,7 @@ func NewStreamBook(symbol string) *StreamOrderBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *StreamOrderBook) BindStream(stream Stream) {
|
func (sb *StreamOrderBook) BindStream(stream Stream) {
|
||||||
stream.OnBookSnapshot(func(book OrderBook) {
|
stream.OnBookSnapshot(func(book SliceOrderBook) {
|
||||||
if sb.Symbol != book.Symbol {
|
if sb.Symbol != book.Symbol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -277,7 +93,7 @@ func (sb *StreamOrderBook) BindStream(stream Stream) {
|
||||||
sb.C.Emit()
|
sb.C.Emit()
|
||||||
})
|
})
|
||||||
|
|
||||||
stream.OnBookUpdate(func(book OrderBook) {
|
stream.OnBookUpdate(func(book SliceOrderBook) {
|
||||||
if sb.Symbol != book.Symbol {
|
if sb.Symbol != book.Symbol {
|
||||||
return
|
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)})
|
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")
|
book := NewRBOrderBook("ETHUSDT")
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for _, ask := range asks {
|
for _, ask := range asks {
|
||||||
|
@ -36,8 +36,8 @@ func BenchmarkOrderBook_Load(b *testing.B) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("OrderBook", func(b *testing.B) {
|
b.Run("SliceOrderBook", func(b *testing.B) {
|
||||||
book := &OrderBook{}
|
book := &SliceOrderBook{}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for _, ask := range asks {
|
for _, ask := range asks {
|
||||||
book.Asks = book.Asks.Upsert(ask, false)
|
book.Asks = book.Asks.Upsert(ask, false)
|
||||||
|
@ -64,7 +64,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
|
||||||
rbBook.Bids.Upsert(bid.Price, bid.Volume)
|
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++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
|
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
|
||||||
if price >= fixedpoint.NewFromFloat(1000) {
|
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 i := 0; i < b.N; i++ {
|
||||||
for _, ask := range asks {
|
for _, ask := range asks {
|
||||||
sliceBook.Asks = sliceBook.Asks.Upsert(ask, false)
|
sliceBook.Asks = sliceBook.Asks.Upsert(ask, false)
|
||||||
|
@ -84,7 +84,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
|
||||||
sliceBook.Bids = sliceBook.Bids.Upsert(bid, true)
|
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++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
|
var price = fixedpoint.NewFromFloat(rand.Float64() * 2000.0)
|
||||||
if price >= fixedpoint.NewFromFloat(1000) {
|
if price >= fixedpoint.NewFromFloat(1000) {
|
||||||
|
@ -97,7 +97,7 @@ func BenchmarkOrderBook_UpdateAndInsert(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrderBook_IsValid(t *testing.T) {
|
func TestOrderBook_IsValid(t *testing.T) {
|
||||||
ob := OrderBook{
|
ob := SliceOrderBook{
|
||||||
Bids: PriceVolumeSlice{
|
Bids: PriceVolumeSlice{
|
||||||
{fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(1.5)},
|
{fixedpoint.NewFromFloat(100.0), fixedpoint.NewFromFloat(1.5)},
|
||||||
{fixedpoint.NewFromFloat(90.0), fixedpoint.NewFromFloat(2.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
|
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 {
|
if current != nil {
|
||||||
cb(current)
|
cb(current)
|
||||||
tree.Preorder(current.Left, cb)
|
tree.PreorderOf(current.Left, cb)
|
||||||
tree.Preorder(current.Right, 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 {
|
if current != nil {
|
||||||
tree.Preorder(current.Left, cb)
|
tree.InorderOf(current.Left, cb)
|
||||||
cb(current)
|
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 {
|
if current != nil {
|
||||||
tree.Preorder(current.Left, cb)
|
tree.PostorderOf(current.Left, cb)
|
||||||
tree.Preorder(current.Right, cb)
|
tree.PostorderOf(current.Right, cb)
|
||||||
cb(current)
|
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)
|
stream.bookUpdateCallbacks = append(stream.bookUpdateCallbacks, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stream *StandardStream) EmitBookUpdate(book OrderBook) {
|
func (stream *StandardStream) EmitBookUpdate(book SliceOrderBook) {
|
||||||
for _, cb := range stream.bookUpdateCallbacks {
|
for _, cb := range stream.bookUpdateCallbacks {
|
||||||
cb(book)
|
cb(book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stream *StandardStream) OnBookSnapshot(cb func(book OrderBook)) {
|
func (stream *StandardStream) OnBookSnapshot(cb func(book SliceOrderBook)) {
|
||||||
stream.bookSnapshotCallbacks = append(stream.bookSnapshotCallbacks, cb)
|
stream.bookSnapshotCallbacks = append(stream.bookSnapshotCallbacks, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stream *StandardStream) EmitBookSnapshot(book OrderBook) {
|
func (stream *StandardStream) EmitBookSnapshot(book SliceOrderBook) {
|
||||||
for _, cb := range stream.bookSnapshotCallbacks {
|
for _, cb := range stream.bookSnapshotCallbacks {
|
||||||
cb(book)
|
cb(book)
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ type StandardStreamEventHub interface {
|
||||||
|
|
||||||
OnKLine(cb func(kline KLine))
|
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)
|
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) {
|
func (stream *StandardStream) Subscribe(channel Channel, symbol string, options SubscribeOptions) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user