Websocket orderbook buffering (#333)

* Initial commit setting up a map orderbook system with a buffer. It will write to the buffer, sort apply to main orderbook and then process.

* Moves namespaces again

* Updates orderbook to use a sweet new WebsocketOrderbookUpdate type to handle all updates whether its using ID or not. So good. Adds many tests

* Starting to implement orderbook update handling per exchange. Updates namespaces again. Hopefuylly will find a way to update via ID not timestamp, too many endpoints dont provide update timestamps

* Changes orderbookbuffer to use BufferUpdate type instead of orderbook.Base to achieve more functionality and no need for type conversion functions. Updates tests

* Updates all instances of ws.orderbook.Update. Simplifies some orderbook logic

* Introduces toggleable buffer. Renames orderbooks. Completes implementation for everywhere but OKGroup due to hash calculation

* Implements orderbook update for okgroup, but forgets about the orderbook hash checking

* Fixes okgroup checksum calculation. Fixes linting issue. Removes redundant Kraken tests.

* Introduces sorting toggle and separates from buffer toggle. Uses benchmarks to highlight performance gains

* Fixes Gemini rate limit and parsing. Removes comments and fixes typos

* Fixes bitfinex orderbook processing

* Inbuilt sorting, minor fixes for websocket implementations. Improves test coverage

* Adds surprise LakeBTC websocket support

* Fixes data race

* Fixes rebasing issues due to namespace movements

* Addresses PR nits: moves folder namespace from ws to websocket. Removes line spaces in imports. Fixes lakebtc websocket returns and defer fucntions. Fixes comments

* Adds poloniex orderook sorting support

* Enables bitstamp and hitbtc orderbook sorting. Fixes poloniex's sorting

* Renames namespaces and combines monitor and connection into wshandler. Removes unused SPOT const. Changes how orderbook stuff is loaded. It is done in startup with a setup. Removes exchange name from loadsnapshot as well

* Removes the connection.go from rebasing issues. Removes error response from functions used in goroutines

* Fixes test with exchange name output change

* Fixes issues where copy and paste and replace all were used poorly
This commit is contained in:
Scott
2019-08-13 09:32:59 +10:00
committed by Adrian Gallagher
parent 2078ba907f
commit 0fbf8b172a
110 changed files with 2197 additions and 1909 deletions

View File

@@ -16,7 +16,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -100,7 +100,7 @@ func (k *Kraken) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
k.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
k.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
k.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets current exchange configuration
@@ -161,6 +161,13 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
k.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
false,
false,
exch.Name)
}
}

View File

@@ -1,18 +1,15 @@
package kraken
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
var k Kraken
@@ -647,107 +644,6 @@ func TestWithdrawCancel(t *testing.T) {
// ---------------------------- Websocket tests -----------------------------------------
// TestOrderbookBufferReset websocket test
func TestOrderbookBufferReset(t *testing.T) {
if k.Name == "" {
k.SetDefaults()
TestSetup(t)
}
if !k.Websocket.IsEnabled() {
t.Skip("Websocket not enabled, skipping")
}
var obUpdates []string
obpartial := `[0,{"as":[["5541.30000","2.50700000","0"]],"bs":[["5541.20000","1.52900000","0"]]}]`
for i := 1; i < orderbookBufferLimit+2; i++ {
obUpdates = append(obUpdates, fmt.Sprintf(`[0,{"a":[["5541.30000","2.50700000","%v"]],"b":[["5541.30000","1.00000000","%v"]]}]`, i, i))
}
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
var dataResponse WebsocketDataResponse
err := common.JSONDecode([]byte(obpartial), &dataResponse)
if err != nil {
t.Errorf("Could not parse, %v", err)
}
obData := dataResponse[1].(map[string]interface{})
channelData := WebsocketChannelData{
ChannelID: 0,
Subscription: "orderbook",
Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"),
}
k.wsProcessOrderBookPartial(
&channelData,
obData,
)
for i := 0; i < len(obUpdates); i++ {
err = common.JSONDecode([]byte(obUpdates[i]), &dataResponse)
if err != nil {
t.Errorf("Could not parse, %v", err)
}
obData = dataResponse[1].(map[string]interface{})
if i < len(obUpdates)-1 {
k.wsProcessOrderBookBuffer(&channelData, obData)
} else if i == len(obUpdates)-1 {
k.wsProcessOrderBookUpdate(&channelData)
k.wsProcessOrderBookBuffer(&channelData, obData)
if len(orderbookBuffer[channelData.ChannelID]) != 1 {
t.Error("Buffer should have 1 entry after being reset")
}
}
}
}
// TestOrderbookBufferReset websocket test
func TestOrderBookOutOfOrder(t *testing.T) {
if k.Name == "" {
k.SetDefaults()
TestSetup(t)
}
if !k.Websocket.IsEnabled() {
t.Skip("Websocket not enabled, skipping")
}
obpartial := `[0,{"as":[["5541.30000","2.50700000","0"]],"bs":[["5541.20000","1.52900000","5"]]}]`
obupdate1 := `[0,{"a":[["5541.30000","0.00000000","1"]],"b":[["5541.30000","0.00000000","3"]]}]`
obupdate2 := `[0,{"a":[["5541.30000","2.50700000","2"]],"b":[["5541.30000","0.00000000","1"]]}]`
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
var dataResponse WebsocketDataResponse
err := common.JSONDecode([]byte(obpartial), &dataResponse)
if err != nil {
t.Errorf("Could not parse, %v", err)
}
obData := dataResponse[1].(map[string]interface{})
channelData := WebsocketChannelData{
ChannelID: 0,
Subscription: "orderbook",
Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"),
}
k.wsProcessOrderBookPartial(
&channelData,
obData,
)
err = common.JSONDecode([]byte(obupdate1), &dataResponse)
if err != nil {
t.Errorf("Could not parse, %v", err)
}
obData = dataResponse[1].(map[string]interface{})
k.wsProcessOrderBookBuffer(&channelData, obData)
err = common.JSONDecode([]byte(obupdate2), &dataResponse)
if err != nil {
t.Errorf("Could not parse, %v", err)
}
obData = dataResponse[1].(map[string]interface{})
k.wsProcessOrderBookBuffer(&channelData, obData)
err = k.wsProcessOrderBookUpdate(&channelData)
if !strings.Contains(err.Error(), "orderbook update out of order") {
t.Error("Expected out of order orderbook error")
}
}
func setupWsTests(t *testing.T) {
if wsSetupRan {
return

View File

@@ -5,16 +5,15 @@ import (
"fmt"
"math"
"net/http"
"sort"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -40,21 +39,13 @@ const (
krakenWsSpread = "spread"
krakenWsOrderbook = "book"
// Only supported asset type
krakenWsAssetType = orderbook.Spot
orderbookBufferLimit = 3
krakenWsRateLimit = 50
)
// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time
var orderbookMutex sync.Mutex
var subscriptionChannelPair []WebsocketChannelData
// krakenOrderBooks TODO THIS IS A TEMPORARY SOLUTION UNTIL ENGINE BRANCH IS MERGED
// WS orderbook data can only rely on WS orderbook data
// Currently REST and WS runs simultaneously, dirtying the data
var krakenOrderBooks map[int64]orderbook.Base
// orderbookBuffer Stores orderbook updates per channel
var orderbookBuffer map[int64][]orderbook.Base
var subscribeToDefaultChannels = true
// Channels require a topic and a currency
@@ -227,22 +218,6 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp
// addNewSubscriptionChannelData stores channel ids, pairs and subscription types to an array
// allowing correlation between subscriptions and returned data
func addNewSubscriptionChannelData(response *WebsocketEventResponse) {
for i := range subscriptionChannelPair {
if response.ChannelID != subscriptionChannelPair[i].ChannelID {
continue
}
// kill the stale orderbooks due to resubscribing
if orderbookBuffer == nil {
orderbookBuffer = make(map[int64][]orderbook.Base)
}
orderbookBuffer[response.ChannelID] = []orderbook.Base{}
if krakenOrderBooks == nil {
krakenOrderBooks = make(map[int64]orderbook.Base)
}
krakenOrderBooks[response.ChannelID] = orderbook.Base{}
return
}
// We change the / to - to maintain compatibility with REST/config
pair := currency.NewPairWithDelimiter(response.Pair.Base.String(), response.Pair.Quote.String(), "-")
subscriptionChannelPair = append(subscriptionChannelPair, WebsocketChannelData{
@@ -346,16 +321,13 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
if asksExist || bidsExist {
k.wsRequestMtx.Lock()
defer k.wsRequestMtx.Unlock()
k.wsProcessOrderBookBuffer(channelData, obData)
if len(orderbookBuffer[channelData.ChannelID]) >= orderbookBufferLimit {
err := k.wsProcessOrderBookUpdate(channelData)
if err != nil {
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
Channel: krakenWsOrderbook,
Currency: channelData.Pair,
}
k.Websocket.ResubscribeToChannel(subscriptionToRemove)
err := k.wsProcessOrderBookUpdate(channelData, obData)
if err != nil {
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
Channel: krakenWsOrderbook,
Currency: channelData.Pair,
}
k.Websocket.ResubscribeToChannel(subscriptionToRemove)
}
}
}
@@ -363,7 +335,7 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
// wsProcessOrderBookPartial creates a new orderbook entry for a given currency pair
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, obData map[string]interface{}) {
ob := orderbook.Base{
base := orderbook.Base{
Pair: channelData.Pair,
AssetType: orderbook.Spot,
}
@@ -375,11 +347,10 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
asks := askData[i].([]interface{})
price, _ := strconv.ParseFloat(asks[0].(string), 64)
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
ob.Asks = append(ob.Asks, orderbook.Item{
base.Asks = append(base.Asks, orderbook.Item{
Amount: amount,
Price: price,
})
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
sec, dec := math.Modf(timeData)
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
@@ -387,17 +358,15 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
highestLastUpdate = askUpdatedTime
}
}
bidData := obData["bs"].([]interface{})
for i := range bidData {
bids := bidData[i].([]interface{})
price, _ := strconv.ParseFloat(bids[0].(string), 64)
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
ob.Bids = append(ob.Bids, orderbook.Item{
base.Bids = append(base.Bids, orderbook.Item{
Amount: amount,
Price: price,
})
timeData, _ := strconv.ParseFloat(bids[2].(string), 64)
sec, dec := math.Modf(timeData)
bidUpdateTime := time.Unix(int64(sec), int64(dec*(1e9)))
@@ -405,33 +374,25 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
highestLastUpdate = bidUpdateTime
}
}
ob.LastUpdated = highestLastUpdate
err := k.Websocket.Orderbook.LoadSnapshot(&ob, k.Name, true)
base.LastUpdated = highestLastUpdate
err := k.Websocket.Orderbook.LoadSnapshot(&base, true)
if err != nil {
k.Websocket.DataHandler <- err
return
}
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: k.Name,
Asset: orderbook.Spot,
Pair: channelData.Pair,
}
if krakenOrderBooks == nil {
krakenOrderBooks = make(map[int64]orderbook.Base)
}
krakenOrderBooks[channelData.ChannelID] = ob
}
func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obData map[string]interface{}) {
ob := orderbook.Base{
AssetType: orderbook.Spot,
ExchangeName: k.Name,
Pair: channelData.Pair,
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, obData map[string]interface{}) error {
update := wsorderbook.WebsocketOrderbookUpdate{
AssetType: krakenWsAssetType,
CurrencyPair: channelData.Pair,
}
var highestLastUpdate time.Time
// Ask data is not always sent
if _, ok := obData["a"]; ok {
@@ -440,11 +401,10 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
asks := askData[i].([]interface{})
price, _ := strconv.ParseFloat(asks[0].(string), 64)
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
ob.Asks = append(ob.Asks, orderbook.Item{
update.Asks = append(update.Asks, orderbook.Item{
Amount: amount,
Price: price,
})
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
sec, dec := math.Modf(timeData)
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
@@ -460,7 +420,7 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
bids := bidData[i].([]interface{})
price, _ := strconv.ParseFloat(bids[0].(string), 64)
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
ob.Bids = append(ob.Bids, orderbook.Item{
update.Bids = append(update.Bids, orderbook.Item{
Amount: amount,
Price: price,
})
@@ -472,193 +432,20 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
}
}
}
ob.LastUpdated = highestLastUpdate
if orderbookBuffer == nil {
orderbookBuffer = make(map[int64][]orderbook.Base)
}
orderbookBuffer[channelData.ChannelID] = append(orderbookBuffer[channelData.ChannelID], ob)
if k.Verbose {
log.Debugf("%v Adding orderbook to buffer for channel %v. Lastupdated: %v. %v / %v",
k.Name,
channelData.ChannelID,
ob.LastUpdated,
len(orderbookBuffer[channelData.ChannelID]),
orderbookBufferLimit)
}
}
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData) error {
if k.Verbose {
log.Debugf("%v Current orderbook 'LastUpdated': %v",
k.Name,
krakenOrderBooks[channelData.ChannelID].LastUpdated)
}
lowestLastUpdated := orderbookBuffer[channelData.ChannelID][0].LastUpdated
if k.Verbose {
log.Debugf("%v Sorting orderbook. Earliest 'LastUpdated' entry: %v",
k.Name,
lowestLastUpdated)
}
sort.Slice(orderbookBuffer[channelData.ChannelID], func(i, j int) bool {
return orderbookBuffer[channelData.ChannelID][i].LastUpdated.Before(orderbookBuffer[channelData.ChannelID][j].LastUpdated)
})
lowestLastUpdated = orderbookBuffer[channelData.ChannelID][0].LastUpdated
if k.Verbose {
log.Debugf("%v Sorted orderbook. Earliest 'LastUpdated' entry: %v",
k.Name,
lowestLastUpdated)
}
// The earliest update has to be after the previously stored orderbook
if krakenOrderBooks[channelData.ChannelID].LastUpdated.After(lowestLastUpdated) {
err := fmt.Errorf("%v orderbook update out of order. Existing: %v, Attempted: %v",
k.Name,
krakenOrderBooks[channelData.ChannelID].LastUpdated,
lowestLastUpdated)
k.Websocket.DataHandler <- err
return err
}
k.updateChannelOrderbookEntries(channelData)
highestLastUpdate := orderbookBuffer[channelData.ChannelID][len(orderbookBuffer[channelData.ChannelID])-1].LastUpdated
if k.Verbose {
log.Debugf("%v Saving orderbook. Lastupdated: %v",
k.Name,
highestLastUpdate)
}
ob := krakenOrderBooks[channelData.ChannelID]
ob.LastUpdated = highestLastUpdate
err := k.Websocket.Orderbook.LoadSnapshot(&ob, k.Name, true)
update.UpdateTime = highestLastUpdate
err := k.Websocket.Orderbook.Update(&update)
if err != nil {
k.Websocket.DataHandler <- err
return err
}
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: k.Name,
Asset: orderbook.Spot,
Pair: channelData.Pair,
}
// Reset the buffer
orderbookBuffer[channelData.ChannelID] = []orderbook.Base{}
return nil
}
func (k *Kraken) updateChannelOrderbookEntries(channelData *WebsocketChannelData) {
for i := 0; i < len(orderbookBuffer[channelData.ChannelID]); i++ {
for j := 0; j < len(orderbookBuffer[channelData.ChannelID][i].Asks); j++ {
k.updateChannelOrderbookAsks(i, j, channelData)
}
for j := 0; j < len(orderbookBuffer[channelData.ChannelID][i].Bids); j++ {
k.updateChannelOrderbookBids(i, j, channelData)
}
}
}
func (k *Kraken) updateChannelOrderbookAsks(i, j int, channelData *WebsocketChannelData) {
askFound := k.updateChannelOrderbookAsk(i, j, channelData)
if !askFound {
if k.Verbose {
log.Debugf("%v Adding Ask for channel %v. Price %v. Amount %v",
k.Name,
channelData.ChannelID,
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount)
}
ob := krakenOrderBooks[channelData.ChannelID]
ob.Asks = append(ob.Asks, orderbookBuffer[channelData.ChannelID][i].Asks[j])
krakenOrderBooks[channelData.ChannelID] = ob
}
}
func (k *Kraken) updateChannelOrderbookAsk(i, j int, channelData *WebsocketChannelData) bool {
askFound := false
for l := 0; l < len(krakenOrderBooks[channelData.ChannelID].Asks); l++ {
if krakenOrderBooks[channelData.ChannelID].Asks[l].Price == orderbookBuffer[channelData.ChannelID][i].Asks[j].Price {
askFound = true
if orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount == 0 {
// Remove existing entry
if k.Verbose {
log.Debugf("%v Removing Ask for channel %v. Price %v. Old amount %v. Buffer %v",
k.Name,
channelData.ChannelID,
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount, i)
}
ob := krakenOrderBooks[channelData.ChannelID]
ob.Asks = append(ob.Asks[:l], ob.Asks[l+1:]...)
krakenOrderBooks[channelData.ChannelID] = ob
l--
} else if krakenOrderBooks[channelData.ChannelID].Asks[l].Amount != orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount {
if k.Verbose {
log.Debugf("%v Updating Ask for channel %v. Price %v. Old amount %v, New Amount %v",
k.Name,
channelData.ChannelID,
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount,
orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount)
}
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount = orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount
}
return askFound
}
}
return askFound
}
func (k *Kraken) updateChannelOrderbookBids(i, j int, channelData *WebsocketChannelData) {
bidFound := k.updateChannelOrderbookBid(i, j, channelData)
if !bidFound {
if k.Verbose {
log.Debugf("%v Adding Bid for channel %v. Price %v. Amount %v",
k.Name,
channelData.ChannelID,
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount)
}
ob := krakenOrderBooks[channelData.ChannelID]
ob.Bids = append(ob.Bids, orderbookBuffer[channelData.ChannelID][i].Bids[j])
krakenOrderBooks[channelData.ChannelID] = ob
}
}
func (k *Kraken) updateChannelOrderbookBid(i, j int, channelData *WebsocketChannelData) bool {
bidFound := false
for l := 0; l < len(krakenOrderBooks[channelData.ChannelID].Bids); l++ {
if krakenOrderBooks[channelData.ChannelID].Bids[l].Price == orderbookBuffer[channelData.ChannelID][i].Bids[j].Price {
bidFound = true
if orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount == 0 {
// Remove existing entry
if k.Verbose {
log.Debugf("%v Removing Bid for channel %v. Price %v. Old amount %v. Buffer %v",
k.Name,
channelData.ChannelID,
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount, i)
}
ob := krakenOrderBooks[channelData.ChannelID]
ob.Bids = append(ob.Bids[:l], ob.Bids[l+1:]...)
krakenOrderBooks[channelData.ChannelID] = ob
l--
} else if krakenOrderBooks[channelData.ChannelID].Bids[l].Amount != orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount {
if k.Verbose {
log.Debugf("%v Updating Bid for channel %v. Price %v. Old amount %v, New Amount %v",
k.Name,
channelData.ChannelID,
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount,
orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount)
}
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount = orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount
}
return bidFound
}
}
return bidFound
}
// wsProcessCandles converts candle data and sends it to the data handler
func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interface{}) {
candleData := data.([]interface{})

View File

@@ -12,7 +12,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)