mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +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:
@@ -2040,28 +2040,29 @@ func TestGetHistoricTrades(t *testing.T) {
|
||||
|
||||
var testOb = orderbook.Base{
|
||||
Asks: []orderbook.Item{
|
||||
{Price: 0.05005, Amount: 0.00000500},
|
||||
{Price: 0.05010, Amount: 0.00000500},
|
||||
{Price: 0.05015, Amount: 0.00000500},
|
||||
{Price: 0.05020, Amount: 0.00000500},
|
||||
{Price: 0.05025, Amount: 0.00000500},
|
||||
{Price: 0.05030, Amount: 0.00000500},
|
||||
{Price: 0.05035, Amount: 0.00000500},
|
||||
{Price: 0.05040, Amount: 0.00000500},
|
||||
{Price: 0.05045, Amount: 0.00000500},
|
||||
{Price: 0.05050, Amount: 0.00000500},
|
||||
// NOTE: 0.00000500 float64 == 0.000005
|
||||
{Price: 0.05005, StrPrice: "0.05005", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05010, StrPrice: "0.05010", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05015, StrPrice: "0.05015", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05020, StrPrice: "0.05020", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05025, StrPrice: "0.05025", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05030, StrPrice: "0.05030", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05035, StrPrice: "0.05035", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05040, StrPrice: "0.05040", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05045, StrPrice: "0.05045", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.05050, StrPrice: "0.05050", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
},
|
||||
Bids: []orderbook.Item{
|
||||
{Price: 0.05000, Amount: 0.00000500},
|
||||
{Price: 0.04995, Amount: 0.00000500},
|
||||
{Price: 0.04990, Amount: 0.00000500},
|
||||
{Price: 0.04980, Amount: 0.00000500},
|
||||
{Price: 0.04975, Amount: 0.00000500},
|
||||
{Price: 0.04970, Amount: 0.00000500},
|
||||
{Price: 0.04965, Amount: 0.00000500},
|
||||
{Price: 0.04960, Amount: 0.00000500},
|
||||
{Price: 0.04955, Amount: 0.00000500},
|
||||
{Price: 0.04950, Amount: 0.00000500},
|
||||
{Price: 0.05000, StrPrice: "0.05000", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04995, StrPrice: "0.04995", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04990, StrPrice: "0.04990", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04980, StrPrice: "0.04980", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04975, StrPrice: "0.04975", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04970, StrPrice: "0.04970", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04965, StrPrice: "0.04965", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04960, StrPrice: "0.04960", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04955, StrPrice: "0.04955", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
{Price: 0.04950, StrPrice: "0.04950", Amount: 0.00000500, StrAmount: "0.00000500"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2079,7 +2080,7 @@ func TestChecksumCalculation(t *testing.T) {
|
||||
t.Errorf("expected %s but received %s", expected, v)
|
||||
}
|
||||
|
||||
err := validateCRC32(&testOb, krakenAPIDocChecksum, 5, 8)
|
||||
err := validateCRC32(&testOb, krakenAPIDocChecksum)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -878,12 +878,13 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[
|
||||
// wsProcessOrderBookPartial creates a new orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, askData, bidData []interface{}) error {
|
||||
base := orderbook.Base{
|
||||
Pair: channelData.Pair,
|
||||
Asset: asset.Spot,
|
||||
VerifyOrderbook: k.CanVerifyOrderbook,
|
||||
Bids: make(orderbook.Items, len(bidData)),
|
||||
Asks: make(orderbook.Items, len(askData)),
|
||||
MaxDepth: channelData.MaxDepth,
|
||||
Pair: channelData.Pair,
|
||||
Asset: asset.Spot,
|
||||
VerifyOrderbook: k.CanVerifyOrderbook,
|
||||
Bids: make(orderbook.Items, len(bidData)),
|
||||
Asks: make(orderbook.Items, len(askData)),
|
||||
MaxDepth: channelData.MaxDepth,
|
||||
ChecksumStringRequired: true,
|
||||
}
|
||||
// Kraken ob data is timestamped per price, GCT orderbook data is
|
||||
// timestamped per entry using the highest last update time, we can attempt
|
||||
@@ -897,21 +898,35 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as
|
||||
if len(asks) < 3 {
|
||||
return errors.New("unexpected asks length")
|
||||
}
|
||||
price, err := strconv.ParseFloat(asks[0].(string), 64)
|
||||
priceStr, ok := asks[0].(string)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("string", asks[0], "price")
|
||||
}
|
||||
price, err := strconv.ParseFloat(priceStr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount, err := strconv.ParseFloat(asks[1].(string), 64)
|
||||
amountStr, ok := asks[1].(string)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("string", asks[1], "amount")
|
||||
}
|
||||
amount, err := strconv.ParseFloat(amountStr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeData, err := strconv.ParseFloat(asks[2].(string), 64)
|
||||
tdStr, ok := asks[2].(string)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("string", asks[2], "time")
|
||||
}
|
||||
timeData, err := strconv.ParseFloat(tdStr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
base.Asks[i] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
StrAmount: amountStr,
|
||||
Price: price,
|
||||
StrPrice: priceStr,
|
||||
}
|
||||
askUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData)
|
||||
if highestLastUpdate.Before(askUpdatedTime) {
|
||||
@@ -927,22 +942,36 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as
|
||||
if len(bids) < 3 {
|
||||
return errors.New("unexpected bids length")
|
||||
}
|
||||
price, err := strconv.ParseFloat(bids[0].(string), 64)
|
||||
priceStr, ok := bids[0].(string)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("string", bids[0], "price")
|
||||
}
|
||||
price, err := strconv.ParseFloat(priceStr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount, err := strconv.ParseFloat(bids[1].(string), 64)
|
||||
amountStr, ok := bids[1].(string)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("string", bids[1], "amount")
|
||||
}
|
||||
amount, err := strconv.ParseFloat(amountStr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeData, err := strconv.ParseFloat(bids[2].(string), 64)
|
||||
tdStr, ok := bids[2].(string)
|
||||
if !ok {
|
||||
return common.GetTypeAssertError("string", bids[2], "time")
|
||||
}
|
||||
timeData, err := strconv.ParseFloat(tdStr, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
base.Bids[i] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
StrAmount: amountStr,
|
||||
Price: price,
|
||||
StrPrice: priceStr,
|
||||
}
|
||||
|
||||
bidUpdateTime := convert.TimeFromUnixTimestampDecimal(timeData)
|
||||
@@ -968,7 +997,6 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask
|
||||
// price and amount as there is no set standard between currency pairs. This
|
||||
// is calculated per update as opposed to snapshot because changes to
|
||||
// decimal amounts could occur at any time.
|
||||
var priceDP, amtDP int
|
||||
var highestLastUpdate time.Time
|
||||
// Ask data is not always sent
|
||||
for i := range askData {
|
||||
@@ -1008,29 +1036,16 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask
|
||||
}
|
||||
|
||||
update.Asks[i] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
StrAmount: amountStr,
|
||||
Price: price,
|
||||
StrPrice: priceStr,
|
||||
}
|
||||
|
||||
askUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData)
|
||||
if highestLastUpdate.Before(askUpdatedTime) {
|
||||
highestLastUpdate = askUpdatedTime
|
||||
}
|
||||
|
||||
if i == len(askData)-1 {
|
||||
pSplit := strings.Split(priceStr, ".")
|
||||
if len(pSplit) != 2 {
|
||||
return errors.New("incorrect decimal data returned for price")
|
||||
}
|
||||
|
||||
priceDP = len(pSplit[1])
|
||||
aSplit := strings.Split(amountStr, ".")
|
||||
if len(aSplit) != 2 {
|
||||
return errors.New("incorrect decimal data returned for amount")
|
||||
}
|
||||
|
||||
amtDP = len(aSplit[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Bid data is not always sent
|
||||
@@ -1071,29 +1086,16 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask
|
||||
}
|
||||
|
||||
update.Bids[i] = orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
StrAmount: amountStr,
|
||||
Price: price,
|
||||
StrPrice: priceStr,
|
||||
}
|
||||
|
||||
bidUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData)
|
||||
if highestLastUpdate.Before(bidUpdatedTime) {
|
||||
highestLastUpdate = bidUpdatedTime
|
||||
}
|
||||
|
||||
if i == len(bidData)-1 {
|
||||
pSplit := strings.Split(priceStr, ".")
|
||||
if len(pSplit) != 2 {
|
||||
return errors.New("incorrect decimal data returned for price")
|
||||
}
|
||||
|
||||
priceDP = len(pSplit[1])
|
||||
aSplit := strings.Split(amountStr, ".")
|
||||
if len(aSplit) != 2 {
|
||||
return errors.New("incorrect decimal data returned for amount")
|
||||
}
|
||||
|
||||
amtDP = len(aSplit[1])
|
||||
}
|
||||
}
|
||||
update.UpdateTime = highestLastUpdate
|
||||
|
||||
@@ -1115,29 +1117,23 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask
|
||||
return err
|
||||
}
|
||||
|
||||
return validateCRC32(book, uint32(token), priceDP, amtDP)
|
||||
return validateCRC32(book, uint32(token))
|
||||
}
|
||||
|
||||
func validateCRC32(b *orderbook.Base, token uint32, decPrice, decAmount int) error {
|
||||
if decPrice == 0 || decAmount == 0 {
|
||||
return fmt.Errorf("%s %s trailing decimal count not calculated",
|
||||
b.Pair,
|
||||
b.Asset)
|
||||
}
|
||||
|
||||
func validateCRC32(b *orderbook.Base, token uint32) error {
|
||||
var checkStr strings.Builder
|
||||
for i := 0; i < 10 && i < len(b.Asks); i++ {
|
||||
priceStr := trim(strconv.FormatFloat(b.Asks[i].Price, 'f', decPrice, 64))
|
||||
checkStr.WriteString(priceStr)
|
||||
amountStr := trim(strconv.FormatFloat(b.Asks[i].Amount, 'f', decAmount, 64))
|
||||
checkStr.WriteString(amountStr)
|
||||
_, err := checkStr.WriteString(trim(b.Asks[i].StrPrice + trim(b.Asks[i].StrAmount)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 10 && i < len(b.Bids); i++ {
|
||||
priceStr := trim(strconv.FormatFloat(b.Bids[i].Price, 'f', decPrice, 64))
|
||||
checkStr.WriteString(priceStr)
|
||||
amountStr := trim(strconv.FormatFloat(b.Bids[i].Amount, 'f', decAmount, 64))
|
||||
checkStr.WriteString(amountStr)
|
||||
_, err := checkStr.WriteString(trim(b.Bids[i].StrPrice) + trim(b.Bids[i].StrAmount))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if check := crc32.ChecksumIEEE([]byte(checkStr.String())); check != token {
|
||||
|
||||
@@ -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