binance: fix depth snapshot buffering

This commit is contained in:
c9s 2021-05-25 21:36:14 +08:00
parent d3f06bc9d7
commit 686dcef2c5
6 changed files with 90 additions and 39 deletions

View File

@ -48,16 +48,8 @@ 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.SliceOrderBook) { streamBook := types.NewStreamBook(symbol)
// log.Infof("book snapshot: %+v", book) streamBook.BindStream(stream)
})
stream.OnBookUpdate(func(book types.SliceOrderBook) {
// log.Infof("book update: %+v", book)
})
streambook := types.NewStreamBook(symbol)
streambook.BindStream(stream)
go func() { go func() {
for { for {
@ -66,8 +58,8 @@ var rootCmd = &cobra.Command{
case <-ctx.Done(): case <-ctx.Done():
return return
case <-streambook.C: case <-streamBook.C:
book := streambook.Copy() book := streamBook.Copy()
if valid, err := book.IsValid(); !valid { if valid, err := book.IsValid(); !valid {
log.Errorf("order book is invalid, error: %v", err) log.Errorf("order book is invalid, error: %v", err)

View File

@ -160,6 +160,18 @@ func (f *DepthFrame) PushEvent(e DepthEvent) {
// drop old events // drop old events
if e.FinalUpdateID <= snapshot.FinalUpdateID { if e.FinalUpdateID <= snapshot.FinalUpdateID {
log.Infof("DROP %s depth update event, updateID %d ~ %d (len %d)",
f.Symbol,
e.FirstUpdateID, e.FinalUpdateID, e.FinalUpdateID-e.FirstUpdateID)
return
}
if e.FirstUpdateID > snapshot.FinalUpdateID+1 {
log.Infof("MISSING %s depth update event, resetting, updateID %d ~ %d (len %d)",
f.Symbol,
e.FirstUpdateID, e.FinalUpdateID, e.FinalUpdateID-e.FirstUpdateID)
f.reset()
return return
} }
@ -181,6 +193,7 @@ func (f *DepthFrame) fetch(ctx context.Context) (*DepthEvent, error) {
} }
event := DepthEvent{ event := DepthEvent{
Symbol: f.Symbol,
FirstUpdateID: 0, FirstUpdateID: 0,
FinalUpdateID: response.LastUpdateID, FinalUpdateID: response.LastUpdateID,
} }

View File

@ -56,14 +56,14 @@ executionReport
type ExecutionReportEvent struct { type ExecutionReportEvent struct {
EventBase EventBase
Symbol string `json:"s"` Symbol string `json:"s"`
Side string `json:"S"` Side string `json:"S"`
ClientOrderID string `json:"c"` ClientOrderID string `json:"c"`
OriginalClientOrderID string `json:"C"` OriginalClientOrderID string `json:"C"`
OrderType string `json:"o"` OrderType string `json:"o"`
OrderCreationTime int64 `json:"O"` OrderCreationTime int64 `json:"O"`
TimeInForce string `json:"f"` TimeInForce string `json:"f"`
IcebergQuantity string `json:"F"` IcebergQuantity string `json:"F"`
@ -71,13 +71,13 @@ type ExecutionReportEvent struct {
OrderQuantity string `json:"q"` OrderQuantity string `json:"q"`
QuoteOrderQuantity string `json:"Q"` QuoteOrderQuantity string `json:"Q"`
OrderPrice string `json:"p"` OrderPrice string `json:"p"`
StopPrice string `json:"P"` StopPrice string `json:"P"`
IsOnBook bool `json:"w"` IsOnBook bool `json:"w"`
IsMaker bool `json:"m"` IsMaker bool `json:"m"`
Ignore bool `json:"M"` Ignore bool `json:"M"`
CommissionAmount string `json:"n"` CommissionAmount string `json:"n"`
CommissionAsset string `json:"N"` CommissionAsset string `json:"N"`
@ -319,17 +319,40 @@ type DepthEvent struct {
Asks []DepthEntry Asks []DepthEntry
} }
func (e *DepthEvent) String() (o string) {
o += fmt.Sprintf("Depth %s bid/ask = ", e.Symbol)
if len(e.Bids) == 0 {
o += "empty"
} else {
o += e.Bids[0].PriceLevel
}
o += "/"
if len(e.Asks) == 0 {
o += "empty"
} else {
o += e.Asks[0].PriceLevel
}
o += fmt.Sprintf(" %d ~ %d", e.FirstUpdateID, e.FinalUpdateID)
return o
}
func (e *DepthEvent) OrderBook() (book types.SliceOrderBook, 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 {
quantity, err := fixedpoint.NewFromString(entry.Quantity) quantity, err := fixedpoint.NewFromString(entry.Quantity)
if err != nil { if err != nil {
log.WithError(err).Errorf("depth quantity parse error: %s", entry.Quantity)
continue continue
} }
price, err := fixedpoint.NewFromString(entry.PriceLevel) price, err := fixedpoint.NewFromString(entry.PriceLevel)
if err != nil { if err != nil {
log.WithError(err).Errorf("depth price parse error: %s", entry.PriceLevel)
continue continue
} }
@ -344,11 +367,13 @@ func (e *DepthEvent) OrderBook() (book types.SliceOrderBook, err error) {
for _, entry := range e.Asks { for _, entry := range e.Asks {
quantity, err := fixedpoint.NewFromString(entry.Quantity) quantity, err := fixedpoint.NewFromString(entry.Quantity)
if err != nil { if err != nil {
log.WithError(err).Errorf("depth quantity parse error: %s", entry.Quantity)
continue continue
} }
price, err := fixedpoint.NewFromString(entry.PriceLevel) price, err := fixedpoint.NewFromString(entry.PriceLevel)
if err != nil { if err != nil {
log.WithError(err).Errorf("depth price parse error: %s", entry.PriceLevel)
continue continue
} }
@ -360,7 +385,7 @@ func (e *DepthEvent) OrderBook() (book types.SliceOrderBook, err error) {
book.Asks = book.Asks.Upsert(pv, false) book.Asks = book.Asks.Upsert(pv, false)
} }
return return book, nil
} }
func parseDepthEntry(val *fastjson.Value) (*DepthEntry, error) { func parseDepthEntry(val *fastjson.Value) (*DepthEntry, error) {

View File

@ -75,7 +75,7 @@ func NewStream(client *binance.Client) *Stream {
stream.OnDepthEvent(func(e *DepthEvent) { stream.OnDepthEvent(func(e *DepthEvent) {
if debugBinanceDepth { if debugBinanceDepth {
log.Infof("received %s depth event updateID %d~%d (len %d)", e.Symbol, e.FirstUpdateID, e.FinalUpdateID, e.FinalUpdateID-e.FirstUpdateID) log.Infof("received %s depth event updateID %d ~ %d (len %d)", e.Symbol, e.FirstUpdateID, e.FinalUpdateID, e.FinalUpdateID-e.FirstUpdateID)
} }
f, ok := stream.depthFrames[e.Symbol] f, ok := stream.depthFrames[e.Symbol]
@ -88,27 +88,29 @@ func NewStream(client *binance.Client) *Stream {
stream.depthFrames[e.Symbol] = f stream.depthFrames[e.Symbol] = f
f.OnReady(func(e DepthEvent, bufEvents []DepthEvent) { f.OnReady(func(snapshotDepth DepthEvent, bufEvents []DepthEvent) {
snapshot, err := e.OrderBook() log.Infof("depth snapshot: %s", snapshotDepth.String())
snapshot, err := snapshotDepth.OrderBook()
if err != nil { if err != nil {
log.WithError(err).Error("book snapshot convert error") log.WithError(err).Error("book snapshot convert error")
return return
} }
if valid, err := snapshot.IsValid(); !valid { if valid, err := snapshot.IsValid(); !valid {
log.Warnf("depth snapshot is invalid, event: %+v, error: %v", e, err) log.Errorf("depth snapshot is invalid, event: %+v, error: %v", snapshotDepth, err)
} }
stream.EmitBookSnapshot(snapshot) stream.EmitBookSnapshot(snapshot)
for _, e := range bufEvents { for _, e := range bufEvents {
book, err := e.OrderBook() bookUpdate, err := e.OrderBook()
if err != nil { if err != nil {
log.WithError(err).Error("book convert error") log.WithError(err).Error("book convert error")
return return
} }
stream.EmitBookUpdate(book) stream.EmitBookUpdate(bookUpdate)
} }
}) })
@ -185,7 +187,7 @@ func NewStream(client *binance.Client) *Stream {
}) })
stream.OnDisconnect(func() { stream.OnDisconnect(func() {
log.Infof("resetting depth snapshot...") log.Infof("resetting depth snapshots...")
for _, f := range stream.depthFrames { for _, f := range stream.depthFrames {
f.reset() f.reset()
} }

View File

@ -52,6 +52,27 @@ func NewMutexOrderBook(symbol string) *MutexOrderBook {
} }
} }
func (b *MutexOrderBook) IsValid() (ok bool, err error) {
b.Lock()
ok, err = b.OrderBook.IsValid()
b.Unlock()
return ok, err
}
func (b *MutexOrderBook) BestBid() (pv PriceVolume, ok bool) {
b.Lock()
pv, ok = b.OrderBook.BestBid()
b.Unlock()
return pv, ok
}
func (b *MutexOrderBook) BestAsk() (pv PriceVolume, ok bool) {
b.Lock()
pv, ok = b.OrderBook.BestAsk()
b.Unlock()
return pv, ok
}
func (b *MutexOrderBook) Load(book SliceOrderBook) { func (b *MutexOrderBook) Load(book SliceOrderBook) {
b.Lock() b.Lock()
b.OrderBook.Load(book) b.OrderBook.Load(book)
@ -66,14 +87,16 @@ func (b *MutexOrderBook) Reset() {
func (b *MutexOrderBook) CopyDepth(depth int) OrderBook { func (b *MutexOrderBook) CopyDepth(depth int) OrderBook {
b.Lock() b.Lock()
defer b.Unlock() book := b.OrderBook.CopyDepth(depth)
return b.OrderBook.CopyDepth(depth) b.Unlock()
return book
} }
func (b *MutexOrderBook) Copy() OrderBook { func (b *MutexOrderBook) Copy() OrderBook {
b.Lock() b.Lock()
defer b.Unlock() book := b.OrderBook.Copy()
return b.OrderBook.Copy() b.Unlock()
return book
} }
func (b *MutexOrderBook) Update(update SliceOrderBook) { func (b *MutexOrderBook) Update(update SliceOrderBook) {

View File

@ -93,10 +93,10 @@ func (b *SliceOrderBook) PriceVolumesBySide(side SideType) PriceVolumeSlice {
switch side { switch side {
case SideTypeBuy: case SideTypeBuy:
return b.Bids return b.Bids.Copy()
case SideTypeSell: case SideTypeSell:
return b.Asks return b.Asks.Copy()
} }
return nil return nil
@ -122,11 +122,6 @@ func (b *SliceOrderBook) updateBids(pvs PriceVolumeSlice) {
} }
} }
func (b *SliceOrderBook) load(book SliceOrderBook) {
b.Reset()
b.update(book)
}
func (b *SliceOrderBook) update(book SliceOrderBook) { func (b *SliceOrderBook) update(book SliceOrderBook) {
b.updateBids(book.Bids) b.updateBids(book.Bids)
b.updateAsks(book.Asks) b.updateAsks(book.Asks)
@ -138,7 +133,8 @@ func (b *SliceOrderBook) Reset() {
} }
func (b *SliceOrderBook) Load(book SliceOrderBook) { func (b *SliceOrderBook) Load(book SliceOrderBook) {
b.load(book) b.Reset()
b.update(book)
b.EmitLoad(b) b.EmitLoad(b)
} }