Files
gocryptotrader/exchanges/orderbook/validate.go
Ryan O'Hara-Reid c892f492a9 buffer/orderbook: shift orderbook update logic from buffer package to orderbook package (#1908)
* buffer/orderbook: shift orderbook update logic from buffer package to orderbook package

* Update exchanges/orderbook/depth.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* linter: fixes

* spelling: fix

* samboss: add in some todos

* sammy nit: add unlock on error

* sammy nits: rm ptr to slice field buffer in orderbookHolder

* sammy nits: Add more coverage bro

* sammy nits: even more coverage

* gk: nits on commentary

* gk: nits change sort.Slice to slices.SortFunc

* gk: fix commentary on buffer clearing

* gk: nits fin

* linter: fix

* Update exchange/websocket/buffer/buffer.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchange/websocket/buffer/buffer.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/orderbook/tranches.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/orderbook/orderbook.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchange/websocket/buffer/buffer_test.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchange/websocket/buffer/buffer_test.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/orderbook/incremental_updates.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* gk: refresh action types and names

* gk nits: consolidate error vars and naming

* gk nits: more name changes

* gk nits; buffer tests update

* gk nits: error var names change

* linter: FIX

* it gets inlined but there is an alloc

* rn field in TODO

* Update exchanges/binance/binance_websocket.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/binance/binance_websocket.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* orderbook: shift verify/validate funcs to validate.go and rn Verify() -> Validate()

* orderbook: validate even in presence of checksum and allow cowboy mode

* buffer; fix test

* kraken: fix futures orderbook by reversing incoming bids

* okx: change default spread pair

* Update exchanges/orderbook/validate.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/orderbook/validate.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/orderbook/validate.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/orderbook/validate.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/orderbook/validate.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* gk: initial nits

* rn fields V(v)erifyorderbook to V(v)alidateOrderbook

* buffer/orderbook: nilguard in validate and change method receiver w -> o

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
2025-06-18 16:19:58 +10:00

97 lines
2.8 KiB
Go

package orderbook
import (
"fmt"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/log"
)
// checker defines specific functionality to determine ascending/descending validation
type checker func(current, previous Level) error
// isAsc specifically defines ascending price check
var isAsc = func(current, previous Level) error {
if current.Price < previous.Price {
return errPriceOutOfOrder
}
return nil
}
// isDsc specifically defines descending price check
var isDsc = func(current, previous Level) error {
if current.Price > previous.Price {
return errPriceOutOfOrder
}
return nil
}
// Validate ensures that the orderbook items are correctly sorted and all fields are valid
// Bids should always go from a high price to a low price and Asks should always go from a low price to a higher price
func (b *Book) Validate() error {
if err := common.NilGuard(b); err != nil {
return err
}
if !b.ValidateOrderbook {
return nil
}
return validate(b)
}
func validate(b *Book) error {
// Some exchanges may return empty sides, but it's not an error
// Options have empty sides too frequently for this warning to be useful
if (len(b.Asks) == 0 || len(b.Bids) == 0) && !b.Asset.IsOptions() {
log.Warnf(log.OrderBook, bookLengthIssue, b.Exchange, b.Pair, b.Asset, len(b.Bids), len(b.Asks))
}
err := checkAlignment(b.Bids, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, b.ChecksumStringRequired, isDsc, b.Exchange)
if err != nil {
return fmt.Errorf(bidLoadBookFailure, b.Exchange, b.Pair, b.Asset, err)
}
err = checkAlignment(b.Asks, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, b.ChecksumStringRequired, isAsc, b.Exchange)
if err != nil {
return fmt.Errorf(askLoadBookFailure, b.Exchange, b.Pair, b.Asset, err)
}
return nil
}
// checkAlignment validates an orderbook side is sequential and does not contain any invalid data
func checkAlignment(depth Levels, fundingRate, priceDuplication, isIDAligned, requiresChecksumString bool, c checker, exch string) error {
for i := range depth {
if depth[i].Price == 0 {
switch {
case exch == "Bitfinex" && fundingRate: /* funding rate can be 0 it seems on Bitfinex */
default:
return ErrPriceZero
}
}
if depth[i].Amount <= 0 {
return errAmountInvalid
}
if fundingRate && depth[i].Period == 0 {
return errPeriodUnset
}
if requiresChecksumString && (depth[i].StrAmount == "" || depth[i].StrPrice == "") {
return errChecksumStringNotSet
}
if i != 0 {
prev := i - 1
if err := c(depth[i], depth[prev]); err != nil {
return err
}
if isIDAligned && depth[i].ID < depth[prev].ID {
return errIDOutOfOrder
}
if !priceDuplication && depth[i].Price == depth[prev].Price {
return errDuplication
}
if depth[i].ID != 0 && depth[i].ID == depth[prev].ID {
return errIDDuplication
}
}
}
return nil
}