mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-05 15:10:59 +00:00
orderbook: Add optional orderbook.Item string fields for potential checksum calculations (#1354)
* orderbook: Add optional orderbook.Item string fields for potential checksum calculations. * glorious: nits * glorious: nits * thrasher: nits * glorious: nits --------- Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
@@ -78,17 +78,18 @@ func (d *Depth) Retrieve() (*Base, error) {
|
||||
return nil, d.validationError
|
||||
}
|
||||
return &Base{
|
||||
Bids: d.bids.retrieve(0),
|
||||
Asks: d.asks.retrieve(0),
|
||||
Exchange: d.exchange,
|
||||
Asset: d.asset,
|
||||
Pair: d.pair,
|
||||
LastUpdated: d.lastUpdated,
|
||||
LastUpdateID: d.lastUpdateID,
|
||||
PriceDuplication: d.priceDuplication,
|
||||
IsFundingRate: d.isFundingRate,
|
||||
VerifyOrderbook: d.VerifyOrderbook,
|
||||
MaxDepth: d.maxDepth,
|
||||
Bids: d.bids.retrieve(0),
|
||||
Asks: d.asks.retrieve(0),
|
||||
Exchange: d.exchange,
|
||||
Asset: d.asset,
|
||||
Pair: d.pair,
|
||||
LastUpdated: d.lastUpdated,
|
||||
LastUpdateID: d.lastUpdateID,
|
||||
PriceDuplication: d.priceDuplication,
|
||||
IsFundingRate: d.isFundingRate,
|
||||
VerifyOrderbook: d.VerifyOrderbook,
|
||||
MaxDepth: d.maxDepth,
|
||||
ChecksumStringRequired: d.checksumStringRequired,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -282,17 +283,18 @@ func (d *Depth) UpdateInsertByID(update *Update) error {
|
||||
func (d *Depth) AssignOptions(b *Base) {
|
||||
d.m.Lock()
|
||||
d.options = options{
|
||||
exchange: b.Exchange,
|
||||
pair: b.Pair,
|
||||
asset: b.Asset,
|
||||
lastUpdated: b.LastUpdated,
|
||||
lastUpdateID: b.LastUpdateID,
|
||||
priceDuplication: b.PriceDuplication,
|
||||
isFundingRate: b.IsFundingRate,
|
||||
VerifyOrderbook: b.VerifyOrderbook,
|
||||
restSnapshot: b.RestSnapshot,
|
||||
idAligned: b.IDAlignment,
|
||||
maxDepth: b.MaxDepth,
|
||||
exchange: b.Exchange,
|
||||
pair: b.Pair,
|
||||
asset: b.Asset,
|
||||
lastUpdated: b.LastUpdated,
|
||||
lastUpdateID: b.LastUpdateID,
|
||||
priceDuplication: b.PriceDuplication,
|
||||
isFundingRate: b.IsFundingRate,
|
||||
VerifyOrderbook: b.VerifyOrderbook,
|
||||
restSnapshot: b.RestSnapshot,
|
||||
idAligned: b.IDAlignment,
|
||||
maxDepth: b.MaxDepth,
|
||||
checksumStringRequired: b.ChecksumStringRequired,
|
||||
}
|
||||
d.m.Unlock()
|
||||
}
|
||||
|
||||
@@ -92,17 +92,18 @@ func TestRetrieve(t *testing.T) {
|
||||
d.asks.load([]Item{{Price: 1337}}, d.stack, time.Now())
|
||||
d.bids.load([]Item{{Price: 1337}}, d.stack, time.Now())
|
||||
d.options = options{
|
||||
exchange: "THE BIG ONE!!!!!!",
|
||||
pair: currency.NewPair(currency.THETA, currency.USD),
|
||||
asset: asset.DownsideProfitContract,
|
||||
lastUpdated: time.Now(),
|
||||
lastUpdateID: 1337,
|
||||
priceDuplication: true,
|
||||
isFundingRate: true,
|
||||
VerifyOrderbook: true,
|
||||
restSnapshot: true,
|
||||
idAligned: true,
|
||||
maxDepth: 10,
|
||||
exchange: "THE BIG ONE!!!!!!",
|
||||
pair: currency.NewPair(currency.THETA, currency.USD),
|
||||
asset: asset.DownsideProfitContract,
|
||||
lastUpdated: time.Now(),
|
||||
lastUpdateID: 1337,
|
||||
priceDuplication: true,
|
||||
isFundingRate: true,
|
||||
VerifyOrderbook: true,
|
||||
restSnapshot: true,
|
||||
idAligned: true,
|
||||
maxDepth: 10,
|
||||
checksumStringRequired: true,
|
||||
}
|
||||
|
||||
// If we add anymore options to the options struct later this will complain
|
||||
|
||||
@@ -100,8 +100,10 @@ updates:
|
||||
// Only apply changes when zero values are not present, Bitmex
|
||||
// for example sends 0 price values.
|
||||
tip.Value.Price = updts[x].Price
|
||||
tip.Value.StrPrice = updts[x].StrPrice
|
||||
}
|
||||
tip.Value.Amount = updts[x].Amount
|
||||
tip.Value.StrAmount = updts[x].StrAmount
|
||||
continue updates
|
||||
}
|
||||
return fmt.Errorf("update error: %w ID: %d not found",
|
||||
@@ -190,7 +192,7 @@ func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLen
|
||||
for x := range updts {
|
||||
for tip := &ll.head; ; tip = &(*tip).Next {
|
||||
if *tip == nil {
|
||||
insertHeadSpecific(ll, updts[x], stack)
|
||||
insertHeadSpecific(ll, &updts[x], stack)
|
||||
break
|
||||
}
|
||||
if (*tip).Value.Price == updts[x].Price { // Match check
|
||||
@@ -198,6 +200,7 @@ func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLen
|
||||
stack.Push(deleteAtTip(ll, tip), tn)
|
||||
} else { // Amend current amount value
|
||||
(*tip).Value.Amount = updts[x].Amount
|
||||
(*tip).Value.StrAmount = updts[x].StrAmount
|
||||
}
|
||||
break // Continue updates
|
||||
}
|
||||
@@ -208,7 +211,7 @@ func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLen
|
||||
// to a non-existent price level (OTC/Hidden order) so we can
|
||||
// break instantly and reduce the traversal of the entire chain.
|
||||
if updts[x].Amount > 0 {
|
||||
insertAtTip(ll, tip, updts[x], stack)
|
||||
insertAtTip(ll, tip, &updts[x], stack)
|
||||
}
|
||||
break // Continue updates
|
||||
}
|
||||
@@ -217,7 +220,7 @@ func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLen
|
||||
// This check below is just a catch all in the event the above
|
||||
// zero value check fails
|
||||
if updts[x].Amount > 0 {
|
||||
insertAtTail(ll, tip, updts[x], stack)
|
||||
insertAtTail(ll, tip, &updts[x], stack)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -255,7 +258,9 @@ updates:
|
||||
if tip.Next == nil {
|
||||
// no movement needed just a re-adjustment
|
||||
tip.Value.Price = updts[x].Price
|
||||
tip.Value.StrPrice = updts[x].StrPrice
|
||||
tip.Value.Amount = updts[x].Amount
|
||||
tip.Value.StrAmount = updts[x].StrAmount
|
||||
continue updates
|
||||
}
|
||||
// bookmark tip to move this node to correct price level
|
||||
@@ -264,6 +269,7 @@ updates:
|
||||
}
|
||||
// no price change, amend amount and continue update
|
||||
tip.Value.Amount = updts[x].Amount
|
||||
tip.Value.StrAmount = updts[x].StrAmount
|
||||
continue updates // continue to next update
|
||||
}
|
||||
|
||||
@@ -305,7 +311,7 @@ updates:
|
||||
}
|
||||
|
||||
if tip.Next == nil {
|
||||
if shiftBookmark(tip, &bookmark, &ll.head, updts[x]) {
|
||||
if shiftBookmark(tip, &bookmark, &ll.head, &updts[x]) {
|
||||
continue updates
|
||||
}
|
||||
}
|
||||
@@ -352,7 +358,7 @@ func (ll *linkedList) insertUpdates(updts Items, stack *stack, comp comparison)
|
||||
}
|
||||
|
||||
if (*tip).Next == nil { // Tail
|
||||
insertAtTail(ll, tip, updts[x], stack)
|
||||
insertAtTail(ll, tip, &updts[x], stack)
|
||||
break // Continue updates
|
||||
}
|
||||
prev = *tip
|
||||
@@ -778,9 +784,9 @@ func deleteAtTip(ll *linkedList, tip **Node) *Node {
|
||||
}
|
||||
|
||||
// insertAtTip inserts at a tip target (can inline)
|
||||
func insertAtTip(ll *linkedList, tip **Node, updt Item, stack *stack) {
|
||||
func insertAtTip(ll *linkedList, tip **Node, updt *Item, stack *stack) {
|
||||
n := stack.Pop()
|
||||
n.Value = updt
|
||||
n.Value = *updt
|
||||
n.Next = *tip
|
||||
n.Prev = (*tip).Prev
|
||||
if (*tip).Prev == nil { // Tip is at head
|
||||
@@ -797,9 +803,9 @@ func insertAtTip(ll *linkedList, tip **Node, updt Item, stack *stack) {
|
||||
}
|
||||
|
||||
// insertAtTail inserts at tail end of node chain (can inline)
|
||||
func insertAtTail(ll *linkedList, tip **Node, updt Item, stack *stack) {
|
||||
func insertAtTail(ll *linkedList, tip **Node, updt *Item, stack *stack) {
|
||||
n := stack.Pop()
|
||||
n.Value = updt
|
||||
n.Value = *updt
|
||||
// Reference tip to new node
|
||||
(*tip).Next = n
|
||||
// Reference new node with current tip
|
||||
@@ -810,9 +816,9 @@ func insertAtTail(ll *linkedList, tip **Node, updt Item, stack *stack) {
|
||||
// insertHeadSpecific inserts at head specifically there might be an instance
|
||||
// where the liquidity on an exchange does fall to zero through a streaming
|
||||
// endpoint then it comes back online. (can inline)
|
||||
func insertHeadSpecific(ll *linkedList, updt Item, stack *stack) {
|
||||
func insertHeadSpecific(ll *linkedList, updt *Item, stack *stack) {
|
||||
n := stack.Pop()
|
||||
n.Value = updt
|
||||
n.Value = *updt
|
||||
ll.head = n
|
||||
ll.length++
|
||||
}
|
||||
@@ -842,12 +848,12 @@ func insertNodeAtBookmark(ll *linkedList, bookmark, n *Node) {
|
||||
|
||||
// shiftBookmark moves a bookmarked node to the tip's next position or if nil,
|
||||
// sets tip as bookmark (can inline)
|
||||
func shiftBookmark(tip *Node, bookmark, head **Node, updt Item) bool {
|
||||
func shiftBookmark(tip *Node, bookmark, head **Node, updt *Item) bool {
|
||||
if *bookmark == nil { // End of the chain and no bookmark set
|
||||
*bookmark = tip // Set tip to bookmark so we can set a new node there
|
||||
return false
|
||||
}
|
||||
(*bookmark).Value = updt
|
||||
(*bookmark).Value = *updt
|
||||
(*bookmark).Next.Prev = (*bookmark).Prev
|
||||
if (*bookmark).Prev == nil { // Bookmark is at head
|
||||
*head = (*bookmark).Next
|
||||
|
||||
@@ -1421,7 +1421,7 @@ func TestShiftBookmark(t *testing.T) {
|
||||
// associate tips prev field with the correct prev node
|
||||
tip.Prev = tipprev
|
||||
|
||||
if !shiftBookmark(tip, &bookmarkedNode, nil, Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
||||
if !shiftBookmark(tip, &bookmarkedNode, nil, &Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
||||
t.Fatal("There should be liquidity so we don't need to set tip to bookmark")
|
||||
}
|
||||
|
||||
@@ -1453,7 +1453,7 @@ func TestShiftBookmark(t *testing.T) {
|
||||
|
||||
var nilBookmark *Node
|
||||
|
||||
if shiftBookmark(tip, &nilBookmark, nil, Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
||||
if shiftBookmark(tip, &nilBookmark, nil, &Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
||||
t.Fatal("there should not be a bookmarked node")
|
||||
}
|
||||
|
||||
@@ -1466,7 +1466,7 @@ func TestShiftBookmark(t *testing.T) {
|
||||
bookmarkedNode.Next = originalBookmarkNext
|
||||
tip.Next = nil
|
||||
|
||||
if !shiftBookmark(tip, &bookmarkedNode, &head, Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
||||
if !shiftBookmark(tip, &bookmarkedNode, &head, &Item{Amount: 1336, ID: 1337, Price: 9999}) {
|
||||
t.Fatal("There should be liquidity so we don't need to set tip to bookmark")
|
||||
}
|
||||
|
||||
|
||||
@@ -225,11 +225,11 @@ func (b *Base) Verify() error {
|
||||
len(b.Bids),
|
||||
len(b.Asks))
|
||||
}
|
||||
err := checkAlignment(b.Bids, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, dsc, b.Exchange)
|
||||
err := checkAlignment(b.Bids, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, b.ChecksumStringRequired, dsc, 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, asc, b.Exchange)
|
||||
err = checkAlignment(b.Asks, b.IsFundingRate, b.PriceDuplication, b.IDAlignment, b.ChecksumStringRequired, asc, b.Exchange)
|
||||
if err != nil {
|
||||
return fmt.Errorf(askLoadBookFailure, b.Exchange, b.Pair, b.Asset, err)
|
||||
}
|
||||
@@ -257,7 +257,7 @@ var dsc = func(current Item, previous Item) error {
|
||||
}
|
||||
|
||||
// checkAlignment validates full orderbook
|
||||
func checkAlignment(depth Items, fundingRate, priceDuplication, isIDAligned bool, c checker, exch string) error {
|
||||
func checkAlignment(depth Items, fundingRate, priceDuplication, isIDAligned, requiresChecksumString bool, c checker, exch string) error {
|
||||
for i := range depth {
|
||||
if depth[i].Price == 0 {
|
||||
switch {
|
||||
@@ -273,6 +273,10 @@ func checkAlignment(depth Items, fundingRate, priceDuplication, isIDAligned bool
|
||||
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 {
|
||||
|
||||
@@ -759,16 +759,29 @@ func TestCheckAlignment(t *testing.T) {
|
||||
Period: 1337,
|
||||
},
|
||||
}
|
||||
err := checkAlignment(itemWithFunding, true, true, false, dsc, "Bitfinex")
|
||||
err := checkAlignment(itemWithFunding, true, true, false, false, dsc, "Bitfinex")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = checkAlignment(itemWithFunding, false, true, false, dsc, "Bitfinex")
|
||||
err = checkAlignment(itemWithFunding, false, true, false, false, dsc, "Bitfinex")
|
||||
if !errors.Is(err, errPriceNotSet) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errPriceNotSet)
|
||||
}
|
||||
err = checkAlignment(itemWithFunding, true, true, false, dsc, "Binance")
|
||||
err = checkAlignment(itemWithFunding, true, true, false, false, dsc, "Binance")
|
||||
if !errors.Is(err, errPriceNotSet) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errPriceNotSet)
|
||||
}
|
||||
|
||||
itemWithFunding[0].Price = 1337
|
||||
err = checkAlignment(itemWithFunding, true, true, false, true, dsc, "Binance")
|
||||
if !errors.Is(err, errChecksumStringNotSet) {
|
||||
t.Fatalf("received: %v but expected: %v", err, errChecksumStringNotSet)
|
||||
}
|
||||
|
||||
itemWithFunding[0].StrAmount = "1337.0000000"
|
||||
itemWithFunding[0].StrPrice = "1337.0000000"
|
||||
err = checkAlignment(itemWithFunding, true, true, false, true, dsc, "Binance")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: %v but expected: %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,18 +21,19 @@ const (
|
||||
|
||||
// Vars for the orderbook package
|
||||
var (
|
||||
errExchangeNameUnset = errors.New("orderbook exchange name not set")
|
||||
errPairNotSet = errors.New("orderbook currency pair not set")
|
||||
errAssetTypeNotSet = errors.New("orderbook asset type not set")
|
||||
errCannotFindOrderbook = errors.New("cannot find orderbook(s)")
|
||||
errPriceNotSet = errors.New("price cannot be zero")
|
||||
errAmountInvalid = errors.New("amount cannot be less or equal to zero")
|
||||
errPriceOutOfOrder = errors.New("pricing out of order")
|
||||
errIDOutOfOrder = errors.New("ID out of order")
|
||||
errDuplication = errors.New("price duplication")
|
||||
errIDDuplication = errors.New("id duplication")
|
||||
errPeriodUnset = errors.New("funding rate period is unset")
|
||||
errNotEnoughLiquidity = errors.New("not enough liquidity")
|
||||
errExchangeNameUnset = errors.New("orderbook exchange name not set")
|
||||
errPairNotSet = errors.New("orderbook currency pair not set")
|
||||
errAssetTypeNotSet = errors.New("orderbook asset type not set")
|
||||
errCannotFindOrderbook = errors.New("cannot find orderbook(s)")
|
||||
errPriceNotSet = errors.New("price cannot be zero")
|
||||
errAmountInvalid = errors.New("amount cannot be less or equal to zero")
|
||||
errPriceOutOfOrder = errors.New("pricing out of order")
|
||||
errIDOutOfOrder = errors.New("ID out of order")
|
||||
errDuplication = errors.New("price duplication")
|
||||
errIDDuplication = errors.New("id duplication")
|
||||
errPeriodUnset = errors.New("funding rate period is unset")
|
||||
errNotEnoughLiquidity = errors.New("not enough liquidity")
|
||||
errChecksumStringNotSet = errors.New("checksum string not set")
|
||||
)
|
||||
|
||||
var service = Service{
|
||||
@@ -57,8 +58,16 @@ type Exchange struct {
|
||||
// Item stores the amount and price values
|
||||
type Item struct {
|
||||
Amount float64
|
||||
Price float64
|
||||
ID int64
|
||||
// StrAmount is a string representation of the amount. e.g. 0.00000100 this
|
||||
// parsed as a float will constrict comparison to 1e-6 not 1e-8 or
|
||||
// potentially will round value which is not ideal.
|
||||
StrAmount string
|
||||
Price float64
|
||||
// StrPrice is a string representation of the price. e.g. 0.00000100 this
|
||||
// parsed as a float will constrict comparison to 1e-6 not 1e-8 or
|
||||
// potentially will round value which is not ideal.
|
||||
StrPrice string
|
||||
ID int64
|
||||
|
||||
// Funding rate field
|
||||
Period int64
|
||||
@@ -100,6 +109,10 @@ type Base struct {
|
||||
// should remove any items that are outside of this scope. Bittrex and
|
||||
// Kraken utilise this field.
|
||||
MaxDepth int
|
||||
// ChecksumStringRequired defines if the checksum is built from the raw
|
||||
// string representations of the price and amount. This helps alleviate any
|
||||
// potential rounding issues.
|
||||
ChecksumStringRequired bool
|
||||
}
|
||||
|
||||
type byOBPrice []Item
|
||||
@@ -109,17 +122,18 @@ func (a byOBPrice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byOBPrice) Less(i, j int) bool { return a[i].Price < a[j].Price }
|
||||
|
||||
type options struct {
|
||||
exchange string
|
||||
pair currency.Pair
|
||||
asset asset.Item
|
||||
lastUpdated time.Time
|
||||
lastUpdateID int64
|
||||
priceDuplication bool
|
||||
isFundingRate bool
|
||||
VerifyOrderbook bool
|
||||
restSnapshot bool
|
||||
idAligned bool
|
||||
maxDepth int
|
||||
exchange string
|
||||
pair currency.Pair
|
||||
asset asset.Item
|
||||
lastUpdated time.Time
|
||||
lastUpdateID int64
|
||||
priceDuplication bool
|
||||
isFundingRate bool
|
||||
VerifyOrderbook bool
|
||||
restSnapshot bool
|
||||
idAligned bool
|
||||
checksumStringRequired bool
|
||||
maxDepth int
|
||||
}
|
||||
|
||||
// Action defines a set of differing states required to implement an incoming
|
||||
|
||||
Reference in New Issue
Block a user