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:
Ryan O'Hara-Reid
2023-10-13 16:38:49 +11:00
committed by GitHub
parent 773441d5a7
commit 51e2e42a19
9 changed files with 205 additions and 168 deletions

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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

View File

@@ -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")
}

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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