Merge branch 'master' into engine

This commit is contained in:
Adrian Gallagher
2019-08-22 18:32:57 +10:00
111 changed files with 2236 additions and 1928 deletions

View File

@@ -39,7 +39,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Huobi.Hadax | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| LakeBTC | Yes | No | NA |
| LakeBTC | Yes | Yes | NA |
| LocalBitcoins | Yes | NA | NA |
| OKCoin International | Yes | Yes | No |
| OKEX | Yes | Yes | No |

View File

@@ -5,6 +5,7 @@
### Current Features
+ REST Support
+ Websocket Support
### How to enable

View File

@@ -40,7 +40,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Huobi.Hadax | Yes | Yes | NA |
| ItBit | Yes | NA | No |
| Kraken | Yes | Yes | NA |
| LakeBTC | Yes | No | NA |
| LakeBTC | Yes | Yes | NA |
| LocalBitcoins | Yes | NA | NA |
| OKCoin International | Yes | Yes | No |
| OKEX | Yes | Yes | No |

View File

@@ -9,7 +9,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"
)
@@ -46,7 +46,7 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() {
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
{{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL
{{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault
{{.Variable}}.Websocket = wshandler.New()
{{.Variable}}.Websocket = monitor.New()
{{.Variable}}.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
{{.Variable}}.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
}

View File

@@ -187,20 +187,20 @@ func ({{.Variable}} *{{.CapitalName}}) GetFeeByType(feeBuilder *exchange.FeeBuil
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle subscribing
func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []monitor.WebsocketChannelSubscription) error {
{{.Variable}}.Websocket.SubscribeToChannels(channels)
return nil
}
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
// which lets websocket.manageSubscriptions handle unsubscribing
func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []monitor.WebsocketChannelSubscription) error {
{{.Variable}}.Websocket.UnubscribeToChannels(channels)
return nil
}
// GetSubscriptions returns a copied list of subscriptions
func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]monitor.WebsocketChannelSubscription, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -40,6 +40,7 @@ const (
configDefaultHTTPTimeout = time.Second * 15
configDefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
configDefaultWebsocketResponseMaxLimit = time.Second * 7
configDefaultWebsocketOrderbookBufferLimit = 5
configMaxAuthFailures = 3
defaultNTPAllowedDifference = 50000000
defaultNTPAllowedNegativeDifference = 50000000
@@ -1024,7 +1025,11 @@ func (c *Config) CheckExchangeConfigValues() error {
c.Exchanges[i].Name, configDefaultWebsocketResponseMaxLimit)
c.Exchanges[i].WebsocketResponseMaxLimit = configDefaultWebsocketResponseMaxLimit
}
if c.Exchanges[i].WebsocketOrderbookBufferLimit <= 0 {
log.Warnf(log.ExchangeSys, "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.",
c.Exchanges[i].Name, configDefaultWebsocketOrderbookBufferLimit)
c.Exchanges[i].WebsocketOrderbookBufferLimit = configDefaultWebsocketOrderbookBufferLimit
}
err := c.CheckPairConsistency(c.Exchanges[i].Name)
if err != nil {
log.Errorf(log.ExchangeSys, "Exchange %s: CheckPairConsistency error: %s\n", c.Exchanges[i].Name, err)

View File

@@ -647,6 +647,7 @@ func TestCheckExchangeConfigValues(t *testing.T) {
checkExchangeConfigValues.Exchanges[0].WebsocketResponseMaxLimit = 0
checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout = 0
checkExchangeConfigValues.Exchanges[0].WebsocketOrderbookBufferLimit = 0
checkExchangeConfigValues.Exchanges[0].HTTPTimeout = 0
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err != nil {
@@ -659,8 +660,8 @@ func TestCheckExchangeConfigValues(t *testing.T) {
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketResponseMaxLimit value", checkExchangeConfigValues.Exchanges[0].Name)
}
if checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout == 0 {
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketResponseCheckTimeout value", checkExchangeConfigValues.Exchanges[0].Name)
if checkExchangeConfigValues.Exchanges[0].WebsocketOrderbookBufferLimit == 0 {
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketOrderbookBufferLimit value", checkExchangeConfigValues.Exchanges[0].Name)
}
if checkExchangeConfigValues.Exchanges[0].HTTPTimeout == 0 {

View File

@@ -57,6 +57,7 @@ type ExchangeConfig struct {
HTTPRateLimiter *HTTPRateLimitConfig `json:"httpRateLimiter,omitempty"`
WebsocketResponseCheckTimeout time.Duration `json:"websocketResponseCheckTimeout"`
WebsocketResponseMaxLimit time.Duration `json:"websocketResponseMaxLimit"`
WebsocketOrderbookBufferLimit int `json:"websocketOrderbookBufferLimit"`
ProxyAddress string `json:"proxyAddress,omitempty"`
BaseCurrencies currency.Currencies `json:"baseCurrencies"`
CurrencyPairs *currency.PairsManager `json:"currencyPairs"`

View File

@@ -14,7 +14,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
"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"
)

View File

@@ -43,7 +43,7 @@ func (n *ntpManager) Start() (err error) {
}()
log.Debugln(log.TimeMgr, "NTP manager starting...")
if Bot.Config.NTPClient.Level == 0 {
if Bot.Config.NTPClient.Level == 0 && *Bot.Config.Logging.Enabled {
// Initial NTP check (prompts user on how we should proceed)
n.inititalCheck = true

View File

@@ -14,7 +14,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
// GetDefaultConfig returns a default exchange config for Alphapoint

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)

View File

@@ -17,7 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -6,7 +6,6 @@ import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
@@ -14,111 +13,14 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
const (
binanceDefaultWebsocketURL = "wss://stream.binance.com:9443"
)
var lastUpdateID map[string]int64
var m sync.Mutex
// SeedLocalCache seeds depth data
func (b *Binance) SeedLocalCache(p currency.Pair) error {
var newOrderBook orderbook.Base
formattedPair := b.FormatExchangeCurrency(p, asset.Spot)
orderbookNew, err := b.GetOrderBook(
OrderBookDataRequestParams{
Symbol: formattedPair.String(),
Limit: 1000,
})
if err != nil {
return err
}
m.Lock()
if lastUpdateID == nil {
lastUpdateID = make(map[string]int64)
}
lastUpdateID[formattedPair.String()] = orderbookNew.LastUpdateID
m.Unlock()
for _, bids := range orderbookNew.Bids {
newOrderBook.Bids = append(newOrderBook.Bids,
orderbook.Item{Amount: bids.Quantity, Price: bids.Price})
}
for _, Asks := range orderbookNew.Asks {
newOrderBook.Asks = append(newOrderBook.Asks,
orderbook.Item{Amount: Asks.Quantity, Price: Asks.Price})
}
newOrderBook.Pair = currency.NewPairFromString(formattedPair.String())
newOrderBook.AssetType = asset.Spot
return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
}
// UpdateLocalCache updates and returns the most recent iteration of the orderbook
func (b *Binance) UpdateLocalCache(ob *WebsocketDepthStream) error {
m.Lock()
ID, ok := lastUpdateID[ob.Pair]
if !ok {
m.Unlock()
return fmt.Errorf("%v - Unable to find lastUpdateID", b.Name)
}
if ob.LastUpdateID+1 <= ID || ID >= ob.LastUpdateID+1 {
// Drop update, out of order
m.Unlock()
return nil
}
lastUpdateID[ob.Pair] = ob.LastUpdateID
m.Unlock()
var updateBid, updateAsk []orderbook.Item
for _, bidsToUpdate := range ob.UpdateBids {
var priceToBeUpdated orderbook.Item
for i, bids := range bidsToUpdate.([]interface{}) {
switch i {
case 0:
priceToBeUpdated.Price, _ = strconv.ParseFloat(bids.(string), 64)
case 1:
priceToBeUpdated.Amount, _ = strconv.ParseFloat(bids.(string), 64)
}
}
updateBid = append(updateBid, priceToBeUpdated)
}
for _, asksToUpdate := range ob.UpdateAsks {
var priceToBeUpdated orderbook.Item
for i, asks := range asksToUpdate.([]interface{}) {
switch i {
case 0:
priceToBeUpdated.Price, _ = strconv.ParseFloat(asks.(string), 64)
case 1:
priceToBeUpdated.Amount, _ = strconv.ParseFloat(asks.(string), 64)
}
}
updateAsk = append(updateAsk, priceToBeUpdated)
}
updatedTime := time.Unix(ob.Timestamp, 0)
currencyPair := currency.NewPairFromString(ob.Pair)
return b.Websocket.Orderbook.Update(updateBid,
updateAsk,
currencyPair,
updatedTime,
b.GetName(),
asset.Spot)
}
// WSConnect intiates a websocket connection
func (b *Binance) WSConnect() error {
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
@@ -174,11 +76,9 @@ func (b *Binance) WSConnect() error {
// WsHandleData handles websocket data from WsReadData
func (b *Binance) WsHandleData() {
b.Websocket.Wg.Add(1)
defer func() {
b.Websocket.Wg.Done()
}()
for {
select {
case <-b.Websocket.ShutdownC:
@@ -233,7 +133,7 @@ func (b *Binance) WsHandleData() {
Price: price,
Amount: amount,
Exchange: b.GetName(),
AssetType: "SPOT",
AssetType: asset.Spot,
Side: trade.EventType,
}
continue
@@ -307,7 +207,7 @@ func (b *Binance) WsHandleData() {
currencyPair := currency.NewPairFromString(depth.Pair)
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: "SPOT",
Asset: asset.Spot,
Exchange: b.GetName(),
}
continue
@@ -315,3 +215,71 @@ func (b *Binance) WsHandleData() {
}
}
}
// SeedLocalCache seeds depth data
func (b *Binance) SeedLocalCache(p currency.Pair) error {
var newOrderBook orderbook.Base
formattedPair := b.FormatExchangeCurrency(p, asset.Spot)
orderbookNew, err := b.GetOrderBook(
OrderBookDataRequestParams{
Symbol: formattedPair.String(),
Limit: 1000,
})
if err != nil {
return err
}
for i := range orderbookNew.Bids {
newOrderBook.Bids = append(newOrderBook.Bids,
orderbook.Item{Amount: orderbookNew.Bids[i].Quantity, Price: orderbookNew.Bids[i].Price})
}
for i := range orderbookNew.Asks {
newOrderBook.Asks = append(newOrderBook.Asks,
orderbook.Item{Amount: orderbookNew.Asks[i].Quantity, Price: orderbookNew.Asks[i].Price})
}
newOrderBook.LastUpdated = time.Unix(orderbookNew.LastUpdateID, 0)
newOrderBook.Pair = currency.NewPairFromString(formattedPair.String())
newOrderBook.AssetType = asset.Spot
return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
}
// UpdateLocalCache updates and returns the most recent iteration of the orderbook
func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error {
var updateBid, updateAsk []orderbook.Item
for i := range wsdp.UpdateBids {
var priceToBeUpdated orderbook.Item
for i, bids := range wsdp.UpdateBids[i].([]interface{}) {
switch i {
case 0:
priceToBeUpdated.Price, _ = strconv.ParseFloat(bids.(string), 64)
case 1:
priceToBeUpdated.Amount, _ = strconv.ParseFloat(bids.(string), 64)
}
}
updateBid = append(updateBid, priceToBeUpdated)
}
for i := range wsdp.UpdateAsks {
var priceToBeUpdated orderbook.Item
for i, asks := range wsdp.UpdateAsks[i].([]interface{}) {
switch i {
case 0:
priceToBeUpdated.Price, _ = strconv.ParseFloat(asks.(string), 64)
case 1:
priceToBeUpdated.Amount, _ = strconv.ParseFloat(asks.(string), 64)
}
}
updateAsk = append(updateAsk, priceToBeUpdated)
}
currencyPair := currency.NewPairFromString(wsdp.Pair)
return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
Bids: updateBid,
Asks: updateAsk,
CurrencyPair: currencyPair,
UpdateID: wsdp.LastUpdateID,
AssetType: asset.Spot,
})
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -98,6 +98,7 @@ func (b *Binance) SetDefaults() {
wshandler.WebsocketOrderbookSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -133,6 +134,14 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
b.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
true,
false,
exch.Name)
return nil
}

View File

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

View File

@@ -13,7 +13,7 @@ import (
"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"
)
// Please supply your own keys here to do better tests

View File

@@ -15,7 +15,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -183,7 +184,11 @@ func (b *Bitfinex) WsDataHandler() {
if stream.Type == websocket.TextMessage {
var result interface{}
common.JSONDecode(stream.Raw, &result)
err = common.JSONDecode(stream.Raw, &result)
if err != nil {
b.Websocket.DataHandler <- err
return
}
switch reflect.TypeOf(result).String() {
case "map[string]interface {}":
eventData := result.(map[string]interface{})
@@ -230,26 +235,19 @@ func (b *Bitfinex) WsDataHandler() {
switch chanInfo.Channel {
case "book":
var newOrderbook []WebsocketBook
curr := currency.NewPairFromString(chanInfo.Pair)
switch len(chanData) {
case 2:
data := chanData[1].([]interface{})
for _, x := range data {
y := x.([]interface{})
for i := range data {
y := data[i].([]interface{})
newOrderbook = append(newOrderbook, WebsocketBook{
Price: y[0].(float64),
Count: int(y[1].(float64)),
Amount: y[2].(float64)})
}
case 4:
newOrderbook = append(newOrderbook, WebsocketBook{
Price: chanData[1].(float64),
Count: int(chanData[2].(float64)),
Amount: chanData[3].(float64)})
}
if len(newOrderbook) > 1 {
err := b.WsInsertSnapshot(currency.NewPairFromString(chanInfo.Pair),
err := b.WsInsertSnapshot(curr,
asset.Spot,
newOrderbook)
@@ -257,18 +255,20 @@ func (b *Bitfinex) WsDataHandler() {
b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s",
err)
}
continue
case 4:
newOrderbook = append(newOrderbook, WebsocketBook{
Price: chanData[1].(float64),
Count: int(chanData[2].(float64)),
Amount: chanData[3].(float64)})
err := b.WsUpdateOrderbook(curr,
asset.Spot,
newOrderbook)
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s",
err)
}
}
err := b.WsUpdateOrderbook(currency.NewPairFromString(chanInfo.Pair),
asset.Spot,
newOrderbook[0])
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s",
err)
}
case "ticker":
b.Websocket.DataHandler <- wshandler.TickerData{
Quantity: chanData[8].(float64),
@@ -285,8 +285,8 @@ func (b *Bitfinex) WsDataHandler() {
case bitfinexWebsocketPositionSnapshot:
var positionSnapshot []WebsocketPosition
data := chanData[2].([]interface{})
for _, x := range data {
y := x.([]interface{})
for i := range data {
y := data[i].([]interface{})
positionSnapshot = append(positionSnapshot,
WebsocketPosition{
Pair: y[0].(string),
@@ -318,8 +318,8 @@ func (b *Bitfinex) WsDataHandler() {
case bitfinexWebsocketWalletSnapshot:
data := chanData[2].([]interface{})
var walletSnapshot []WebsocketWallet
for _, x := range data {
y := x.([]interface{})
for i := range data {
y := data[i].([]interface{})
walletSnapshot = append(walletSnapshot,
WebsocketWallet{
Name: y[0].(string),
@@ -343,8 +343,8 @@ func (b *Bitfinex) WsDataHandler() {
case bitfinexWebsocketOrderSnapshot:
var orderSnapshot []WebsocketOrder
data := chanData[2].([]interface{})
for _, x := range data {
y := x.([]interface{})
for i := range data {
y := data[i].([]interface{})
orderSnapshot = append(orderSnapshot,
WebsocketOrder{
OrderID: int64(y[0].(float64)),
@@ -407,8 +407,8 @@ func (b *Bitfinex) WsDataHandler() {
switch len(chanData) {
case 2:
data := chanData[1].([]interface{})
for _, x := range data {
y := x.([]interface{})
for i := range data {
y := data[i].([]interface{})
if _, ok := y[0].(string); ok {
continue
}
@@ -463,31 +463,26 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books
if len(books) == 0 {
return errors.New("bitfinex.go error - no orderbooks submitted")
}
var bid, ask []orderbook.Item
for _, book := range books {
if book.Amount >= 0 {
bid = append(bid, orderbook.Item{Amount: book.Amount, Price: book.Price})
for i := range books {
if books[i].Amount >= 0 {
bid = append(bid, orderbook.Item{Amount: books[i].Amount, Price: books[i].Price})
} else {
ask = append(ask, orderbook.Item{Amount: book.Amount * -1, Price: book.Price})
ask = append(ask, orderbook.Item{Amount: books[i].Amount * -1, Price: books[i].Price})
}
}
if len(bid) == 0 && len(ask) == 0 {
return errors.New("bitfinex.go error - no orderbooks in item lists")
}
var newOrderBook orderbook.Base
newOrderBook.Asks = ask
newOrderBook.AssetType = assetType
newOrderBook.Bids = bid
newOrderBook.Pair = p
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
if err != nil {
return fmt.Errorf("bitfinex.go error - %s", err)
}
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
@@ -496,80 +491,36 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books
// WsUpdateOrderbook updates the orderbook list, removing and adding to the
// orderbook sides
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book WebsocketBook) error {
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book []WebsocketBook) error {
orderbookUpdate := wsorderbook.WebsocketOrderbookUpdate{
Asks: []orderbook.Item{},
Bids: []orderbook.Item{},
AssetType: assetType,
CurrencyPair: p,
}
if book.Count > 0 {
if book.Amount > 0 {
// Update/add bid
newBidPrice := orderbook.Item{Price: book.Price, Amount: book.Amount}
err := b.Websocket.Orderbook.Update([]orderbook.Item{newBidPrice},
nil,
p,
time.Now(),
b.GetName(),
assetType)
if err != nil {
return err
for i := 0; i < len(book); i++ {
switch {
case book[i].Count > 0:
if book[i].Amount > 0 {
// update bid
orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: book[i].Amount, Price: book[i].Price})
} else if book[i].Amount < 0 {
// update ask
orderbookUpdate.Asks = append(orderbookUpdate.Asks, orderbook.Item{Amount: book[i].Amount * -1, Price: book[i].Price})
}
case book[i].Count == 0:
if book[i].Amount == 1 {
// delete bid
orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: 0, Price: book[i].Price})
} else if book[i].Amount == -1 {
// delete ask
orderbookUpdate.Asks = append(orderbookUpdate.Asks, orderbook.Item{Amount: 0, Price: book[i].Price})
}
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
return nil
}
// Update/add ask
newAskPrice := orderbook.Item{Price: book.Price, Amount: book.Amount * -1}
err := b.Websocket.Orderbook.Update(nil,
[]orderbook.Item{newAskPrice},
p,
time.Now(),
b.GetName(),
assetType)
if err != nil {
return err
}
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
return nil
}
if book.Amount == 1 {
// Remove bid
bidPriceRemove := orderbook.Item{Price: book.Price, Amount: 0}
err := b.Websocket.Orderbook.Update([]orderbook.Item{bidPriceRemove},
nil,
p,
time.Now(),
b.GetName(),
assetType)
if err != nil {
return err
}
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
Asset: assetType,
Exchange: b.GetName()}
return nil
}
// Remove from ask
askPriceRemove := orderbook.Item{Price: book.Price, Amount: 0}
err := b.Websocket.Orderbook.Update(nil,
[]orderbook.Item{askPriceRemove},
p,
time.Now(),
b.GetName(),
assetType)
orderbookUpdate.UpdateTime = time.Now()
err := b.Websocket.Orderbook.Update(&orderbookUpdate)
if err != nil {
return err
}

View File

@@ -17,7 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -99,6 +99,7 @@ func (b *Bitfinex) SetDefaults() {
wshandler.WebsocketAuthenticatedEndpointsSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -134,6 +135,14 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
b.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
false,
false,
false,
exch.Name)
return nil
}

View File

@@ -14,7 +14,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)

View File

@@ -74,31 +74,27 @@ func (b *Bithumb) GetTradablePairs() ([]string, error) {
//
// symbol e.g. "btc"
func (b *Bithumb) GetTicker(symbol string) (Ticker, error) {
response := Ticker{}
path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, strings.ToUpper(symbol))
var response TickerResponse
path := fmt.Sprintf("%s%s%s",
b.API.Endpoints.URL,
publicTicker,
strings.ToUpper(symbol))
err := b.SendHTTPRequest(path, &response)
if err != nil {
return response, err
return response.Data, err
}
if response.Status != noError {
return response, errors.New(response.Message)
return response.Data, errors.New(response.Message)
}
return response, nil
return response.Data, nil
}
// GetAllTickers returns all ticker information
func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) {
type Response struct {
ActionStatus
Data map[string]interface{}
}
response := Response{}
var response TickersResponse
path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, "all")
err := b.SendHTTPRequest(path, &response)
if err != nil {
return nil, err
@@ -113,25 +109,12 @@ func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) {
if k == "date" {
continue
}
if reflect.TypeOf(v).String() != "map[string]interface {}" {
continue
var newTicker Ticker
err := common.JSONDecode(v, &newTicker)
if err != nil {
return nil, err
}
data := v.(map[string]interface{})
var t Ticker
t.AveragePrice, _ = strconv.ParseFloat(data["average_price"].(string), 64)
t.BuyPrice, _ = strconv.ParseFloat(data["buy_price"].(string), 64)
t.ClosingPrice, _ = strconv.ParseFloat(data["closing_price"].(string), 64)
t.MaxPrice, _ = strconv.ParseFloat(data["max_price"].(string), 64)
t.MinPrice, _ = strconv.ParseFloat(data["min_price"].(string), 64)
t.OpeningPrice, _ = strconv.ParseFloat(data["opening_price"].(string), 64)
t.SellPrice, _ = strconv.ParseFloat(data["sell_price"].(string), 64)
t.UnitsTraded, _ = strconv.ParseFloat(data["units_traded"].(string), 64)
t.Volume1Day, _ = strconv.ParseFloat(data["volume_1day"].(string), 64)
t.Volume7Day, _ = strconv.ParseFloat(data["volume_7day"].(string), 64)
result[k] = t
result[k] = newTicker
}
return result, nil
}
@@ -527,7 +510,7 @@ func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, r
err = common.JSONDecode(intermediary, &errCapture)
if err == nil {
if errCapture.Status != "" && errCapture.Status != "0000" {
if errCapture.Status != "" && errCapture.Status != noError {
return fmt.Errorf("sendAuthenticatedAPIRequest error code: %s message:%s",
errCapture.Status,
errCode[errCapture.Status])

View File

@@ -1,21 +1,25 @@
package bithumb
import "github.com/thrasher-corp/gocryptotrader/currency"
import (
"encoding/json"
"github.com/thrasher-corp/gocryptotrader/currency"
)
// Ticker holds ticker data
type Ticker struct {
OpeningPrice float64 `json:"opening_price,string"`
ClosingPrice float64 `json:"closing_price,string"`
MinPrice float64 `json:"min_price,string"`
MaxPrice float64 `json:"max_price,string"`
AveragePrice float64 `json:"average_price,string"`
UnitsTraded float64 `json:"units_traded,string"`
Volume1Day float64 `json:"volume_1day,string"`
Volume7Day float64 `json:"volume_7day,string"`
BuyPrice float64 `json:"buy_price,string"`
SellPrice float64 `json:"sell_price,string"`
ActionStatus
// Date int64 `json:"date,string"`
OpeningPrice float64 `json:"opening_price,string"`
ClosingPrice float64 `json:"closing_price,string"`
MinPrice float64 `json:"min_price,string"`
MaxPrice float64 `json:"max_price,string"`
UnitsTraded float64 `json:"units_traded,string"`
AccumulatedTradeValue float64 `json:"acc_trade_value,string"`
PreviousClosingPrice float64 `json:"prev_closing_price,string"`
UnitsTraded24Hr float64 `json:"units_traded_24H,string"`
AccumulatedTradeValue24hr float64 `json:"acc_trade_value_24H,string"`
Fluctate24Hr string `json:"fluctate_24H"`
FluctateRate24hr float64 `json:"fluctate_rate_24H,string"`
Date int64 `json:"date,string"`
}
// TickerResponse holds the standard ticker response
@@ -27,9 +31,9 @@ type TickerResponse struct {
// TickersResponse holds the standard ticker response
type TickersResponse struct {
Status string `json:"status"`
Data map[string]Ticker `json:"data"`
Message string `json:"message"`
Status string `json:"status"`
Data map[string]json.RawMessage `json:"data"`
Message string `json:"message"`
}
// Orderbook holds full range of order book information

View File

@@ -17,7 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -164,11 +164,9 @@ func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr
currency := x.Base.String()
var tp ticker.Price
tp.Pair = x
tp.Ask = tickers[currency].SellPrice
tp.Bid = tickers[currency].BuyPrice
tp.Low = tickers[currency].MinPrice
tp.Last = tickers[currency].ClosingPrice
tp.Volume = tickers[currency].Volume1Day
tp.Volume = tickers[currency].UnitsTraded24Hr
tp.High = tickers[currency].MaxPrice
err = ticker.ProcessTicker(b.Name, &tp, assetType)

View File

@@ -13,7 +13,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
// Bitmex is the overarching type across this package

View File

@@ -12,7 +12,7 @@ import (
"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"
)
// Please supply your own keys here for due diligence testing

View File

@@ -15,7 +15,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -222,23 +223,22 @@ func (b *Bitmex) wsHandleIncomingData() {
continue
}
for _, trade := range trades.Data {
for i := range trades.Data {
var timestamp time.Time
timestamp, err = time.Parse(time.RFC3339, trade.Timestamp)
timestamp, err = time.Parse(time.RFC3339, trades.Data[i].Timestamp)
if err != nil {
b.Websocket.DataHandler <- err
continue
}
// TODO: update this to support multiple asset types
b.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: timestamp,
Price: trade.Price,
Amount: float64(trade.Size),
CurrencyPair: currency.NewPairFromString(trade.Symbol),
Price: trades.Data[i].Price,
Amount: float64(trades.Data[i].Size),
CurrencyPair: currency.NewPairFromString(trades.Data[i].Symbol),
Exchange: b.GetName(),
AssetType: "CONTRACT",
Side: trade.Side,
Side: trades.Data[i].Side,
}
}
@@ -328,99 +328,80 @@ func (b *Bitmex) wsHandleIncomingData() {
}
}
var snapshotloaded = make(map[currency.Pair]map[asset.Item]bool)
// ProcessOrderbook processes orderbook updates
func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType asset.Item) error { // nolint: unparam
if len(data) < 1 {
return errors.New("bitmex_websocket.go error - no orderbook data")
}
_, ok := snapshotloaded[currencyPair]
if !ok {
snapshotloaded[currencyPair] = make(map[asset.Item]bool)
}
_, ok = snapshotloaded[currencyPair][assetType]
if !ok {
snapshotloaded[currencyPair][assetType] = false
}
switch action {
case bitmexActionInitialData:
if !snapshotloaded[currencyPair][assetType] {
var newOrderBook orderbook.Base
var bids, asks []orderbook.Item
for _, orderbookItem := range data {
if strings.EqualFold(orderbookItem.Side, exchange.SellOrderSide.ToString()) {
asks = append(asks, orderbook.Item{
Price: orderbookItem.Price,
Amount: float64(orderbookItem.Size),
})
continue
}
bids = append(bids, orderbook.Item{
Price: orderbookItem.Price,
Amount: float64(orderbookItem.Size),
var newOrderBook orderbook.Base
var bids, asks []orderbook.Item
for i := range data {
if strings.EqualFold(data[i].Side, exchange.SellOrderSide.ToString()) {
asks = append(asks, orderbook.Item{
Price: data[i].Price,
Amount: float64(data[i].Size),
})
continue
}
if len(bids) == 0 || len(asks) == 0 {
return errors.New("bitmex_websocket.go error - snapshot not initialised correctly")
}
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.AssetType = assetType
newOrderBook.Pair = currencyPair
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
if err != nil {
return fmt.Errorf("bitmex_websocket.go process orderbook error - %s",
err)
}
snapshotloaded[currencyPair][assetType] = true
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: assetType,
Exchange: b.GetName(),
}
bids = append(bids, orderbook.Item{
Price: data[i].Price,
Amount: float64(data[i].Size),
})
}
if len(bids) == 0 || len(asks) == 0 {
return errors.New("bitmex_websocket.go error - snapshot not initialised correctly")
}
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.AssetType = assetType
newOrderBook.Pair = currencyPair
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
if err != nil {
return fmt.Errorf("bitmex_websocket.go process orderbook error - %s",
err)
}
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: assetType,
Exchange: b.GetName(),
}
default:
if snapshotloaded[currencyPair][assetType] {
var asks, bids []orderbook.Item
for _, orderbookItem := range data {
if orderbookItem.Side == "Sell" {
asks = append(asks, orderbook.Item{
Price: orderbookItem.Price,
Amount: float64(orderbookItem.Size),
})
continue
}
bids = append(bids, orderbook.Item{
Price: orderbookItem.Price,
Amount: float64(orderbookItem.Size),
var asks, bids []orderbook.Item
for i := range data {
if strings.EqualFold(data[i].Side, "Sell") {
asks = append(asks, orderbook.Item{
Price: data[i].Price,
Amount: float64(data[i].Size),
})
continue
}
bids = append(bids, orderbook.Item{
Price: data[i].Price,
Amount: float64(data[i].Size),
})
}
err := b.Websocket.Orderbook.UpdateUsingID(bids,
asks,
currencyPair,
b.GetName(),
assetType,
action)
err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: currencyPair,
UpdateTime: time.Now(),
AssetType: assetType,
Action: action,
})
if err != nil {
return err
}
if err != nil {
return err
}
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: assetType,
Exchange: b.GetName(),
}
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: currencyPair,
Asset: assetType,
Exchange: b.GetName(),
}
}
return nil

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -123,6 +123,7 @@ func (b *Bitmex) SetDefaults() {
wshandler.WebsocketDeadMansSwitchSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup takes in the supplied exchange configuration details and sets params
@@ -158,6 +159,14 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
b.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
false,
false,
true,
exch.Name)
return nil
}

View File

@@ -14,7 +14,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -6,14 +6,14 @@ import (
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -157,22 +157,21 @@ func (b *Bitstamp) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubs
return b.WebsocketConn.SendMessage(req)
}
func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType asset.Item) error {
if len(ob.Asks) == 0 && len(ob.Bids) == 0 {
func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, assetType asset.Item) error {
if len(update.Asks) == 0 && len(update.Bids) == 0 {
return errors.New("bitstamp_websocket.go error - no orderbook data")
}
var asks, bids []orderbook.Item
if len(ob.Asks) > 0 {
for _, ask := range ob.Asks {
target, err := strconv.ParseFloat(ask[0], 64)
if len(update.Asks) > 0 {
for i := range update.Asks {
target, err := strconv.ParseFloat(update.Asks[i][0], 64)
if err != nil {
b.Websocket.DataHandler <- err
continue
}
amount, err := strconv.ParseFloat(ask[1], 64)
amount, err := strconv.ParseFloat(update.Asks[i][1], 64)
if err != nil {
b.Websocket.DataHandler <- err
continue
@@ -182,15 +181,15 @@ func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, ass
}
}
if len(ob.Bids) > 0 {
for _, bid := range ob.Bids {
target, err := strconv.ParseFloat(bid[0], 64)
if len(update.Bids) > 0 {
for i := range update.Bids {
target, err := strconv.ParseFloat(update.Bids[i][0], 64)
if err != nil {
b.Websocket.DataHandler <- err
continue
}
amount, err := strconv.ParseFloat(bid[1], 64)
amount, err := strconv.ParseFloat(update.Bids[i][1], 64)
if err != nil {
b.Websocket.DataHandler <- err
continue
@@ -199,8 +198,13 @@ func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, ass
bids = append(bids, orderbook.Item{Price: target, Amount: amount})
}
}
err := b.Websocket.Orderbook.Update(bids, asks, p, time.Now(), b.GetName(), assetType)
err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: p,
UpdateID: update.Timestamp,
AssetType: asset.Spot,
})
if err != nil {
return err
}
@@ -225,17 +229,17 @@ func (b *Bitstamp) seedOrderBook() error {
var newOrderBook orderbook.Base
var asks, bids []orderbook.Item
for _, ask := range orderbookSeed.Asks {
for i := range orderbookSeed.Asks {
var item orderbook.Item
item.Amount = ask.Amount
item.Price = ask.Price
item.Amount = orderbookSeed.Asks[i].Amount
item.Price = orderbookSeed.Asks[i].Price
asks = append(asks, item)
}
for _, bid := range orderbookSeed.Bids {
for i := range orderbookSeed.Bids {
var item orderbook.Item
item.Amount = bid.Amount
item.Price = bid.Price
item.Amount = orderbookSeed.Bids[i].Amount
item.Price = orderbookSeed.Bids[i].Price
bids = append(bids, item)
}
@@ -244,7 +248,7 @@ func (b *Bitstamp) seedOrderBook() error {
newOrderBook.Pair = p[x]
newOrderBook.AssetType = asset.Spot
err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
if err != nil {
return err
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -96,6 +96,7 @@ func (b *Bitstamp) SetDefaults() {
wshandler.WebsocketUnsubscribeSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets configuration values to bitstamp
@@ -131,6 +132,14 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
b.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
true,
false,
exch.Name)
return nil
}

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)

View File

@@ -11,7 +11,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -1,8 +1,10 @@
package btse
import "time"
// Market stores market data
type Market struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
BaseCurrency string `json:"base_currency"`
QuoteCurrency string `json:"quote_currency"`
BaseMinSize float64 `json:"base_min_size"`
@@ -51,8 +53,8 @@ type MarketStatistics struct {
// ServerTime stores the server time data
type ServerTime struct {
ISO string `json:"iso"`
Epoch float64 `json:"epoch"`
ISO time.Time `json:"iso"`
Epoch string `json:"epoch"`
}
// CurrencyBalance stores the account info data

View File

@@ -13,7 +13,7 @@ import (
"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"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -64,7 +64,7 @@ func (b *BTSE) WsHandleData() {
ProductID string `json:"product_id"`
}
if strings.Contains(string(resp.Raw), "Welcome to BTSE") {
if strings.Contains(string(resp.Raw), "Connected. Welcome to BTSE!") {
if b.Verbose {
log.Debugf(log.ExchangeSys, "%s websocket client successfully connected to %s",
b.Name, b.Websocket.GetWebsocketURL())
@@ -122,14 +122,14 @@ func (b *BTSE) WsHandleData() {
// ProcessSnapshot processes the initial orderbook snap shot
func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
var base orderbook.Base
for _, bid := range snapshot.Bids {
p := strings.Replace(bid[0].(string), ",", "", -1)
for i := range snapshot.Bids {
p := strings.Replace(snapshot.Bids[i][0].(string), ",", "", -1)
price, err := strconv.ParseFloat(p, 64)
if err != nil {
return err
}
a := strings.Replace(bid[1].(string), ",", "", -1)
a := strings.Replace(snapshot.Bids[i][1].(string), ",", "", -1)
amount, err := strconv.ParseFloat(a, 64)
if err != nil {
return err
@@ -139,14 +139,14 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
orderbook.Item{Price: price, Amount: amount})
}
for _, ask := range snapshot.Asks {
p := strings.Replace(ask[0].(string), ",", "", -1)
for i := range snapshot.Asks {
p := strings.Replace(snapshot.Asks[i][0].(string), ",", "", -1)
price, err := strconv.ParseFloat(p, 64)
if err != nil {
return err
}
a := strings.Replace(ask[1].(string), ",", "", -1)
a := strings.Replace(snapshot.Asks[i][1].(string), ",", "", -1)
amount, err := strconv.ParseFloat(a, 64)
if err != nil {
return err
@@ -162,7 +162,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
base.LastUpdated = time.Now()
base.ExchangeName = b.Name
err := base.Process()
err := b.Websocket.Orderbook.LoadSnapshot(&base, true)
if err != nil {
return err
}

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -93,6 +93,7 @@ func (b *BTSE) SetDefaults() {
wshandler.WebsocketUnsubscribeSupported
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
@@ -129,6 +130,14 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
b.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
false,
false,
false,
false,
exch.Name)
return nil
}
@@ -166,7 +175,10 @@ func (b *BTSE) FetchTradablePairs(asset asset.Item) ([]string, error) {
var pairs []string
for _, m := range *markets {
pairs = append(pairs, m.ID)
if m.Status != "active" {
continue
}
pairs = append(pairs, m.Symbol)
}
return pairs, nil

View File

@@ -13,7 +13,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -10,7 +10,7 @@ import (
"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 c CoinbasePro

View File

@@ -14,7 +14,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
const (
@@ -179,13 +180,13 @@ func (c *CoinbasePro) WsHandleData() {
// ProcessSnapshot processes the initial orderbook snap shot
func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) error {
var base orderbook.Base
for _, bid := range snapshot.Bids {
price, err := strconv.ParseFloat(bid[0].(string), 64)
for i := range snapshot.Bids {
price, err := strconv.ParseFloat(snapshot.Bids[i][0].(string), 64)
if err != nil {
return err
}
amount, err := strconv.ParseFloat(bid[1].(string), 64)
amount, err := strconv.ParseFloat(snapshot.Bids[i][1].(string), 64)
if err != nil {
return err
}
@@ -194,13 +195,13 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
orderbook.Item{Price: price, Amount: amount})
}
for _, ask := range snapshot.Asks {
price, err := strconv.ParseFloat(ask[0].(string), 64)
for i := range snapshot.Asks {
price, err := strconv.ParseFloat(snapshot.Asks[i][0].(string), 64)
if err != nil {
return err
}
amount, err := strconv.ParseFloat(ask[1].(string), 64)
amount, err := strconv.ParseFloat(snapshot.Asks[i][1].(string), 64)
if err != nil {
return err
}
@@ -209,18 +210,17 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
orderbook.Item{Price: price, Amount: amount})
}
p := currency.NewPairFromString(snapshot.ProductID)
pair := currency.NewPairFromString(snapshot.ProductID)
base.AssetType = asset.Spot
base.Pair = p
base.LastUpdated = time.Now()
base.Pair = pair
err := c.Websocket.Orderbook.LoadSnapshot(&base, c.GetName(), false)
err := c.Websocket.Orderbook.LoadSnapshot(&base, false)
if err != nil {
return err
}
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Pair: pair,
Asset: asset.Spot,
Exchange: c.GetName(),
}
@@ -230,26 +230,35 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
// ProcessUpdate updates the orderbook local cache
func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error {
var Asks, Bids []orderbook.Item
var asks, bids []orderbook.Item
for _, data := range update.Changes {
price, _ := strconv.ParseFloat(data[1].(string), 64)
volume, _ := strconv.ParseFloat(data[2].(string), 64)
for i := range update.Changes {
price, _ := strconv.ParseFloat(update.Changes[i][1].(string), 64)
volume, _ := strconv.ParseFloat(update.Changes[i][2].(string), 64)
if data[0].(string) == exchange.BuyOrderSide.ToLower().ToString() {
Bids = append(Bids, orderbook.Item{Price: price, Amount: volume})
if update.Changes[i][0].(string) == exchange.BuyOrderSide.ToLower().ToString() {
bids = append(bids, orderbook.Item{Price: price, Amount: volume})
} else {
Asks = append(Asks, orderbook.Item{Price: price, Amount: volume})
asks = append(asks, orderbook.Item{Price: price, Amount: volume})
}
}
if len(Asks) == 0 && len(Bids) == 0 {
return errors.New("coibasepro_websocket.go error - no data in websocket update")
if len(asks) == 0 && len(bids) == 0 {
return errors.New("coinbasepro_websocket.go error - no data in websocket update")
}
p := currency.NewPairFromString(update.ProductID)
err := c.Websocket.Orderbook.Update(Bids, Asks, p, time.Now(), c.GetName(), asset.Spot)
timestamp, err := time.Parse(time.RFC3339, update.Time)
if err != nil {
return err
}
err = c.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: p,
UpdateTime: timestamp,
AssetType: asset.Spot,
})
if err != nil {
return err
}

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -99,6 +99,7 @@ func (c *CoinbasePro) SetDefaults() {
wshandler.WebsocketSequenceNumberSupported
c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup initialises the exchange parameters with the current configuration
@@ -134,6 +135,14 @@ func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
c.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
false,
false,
exch.Name)
return nil
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -10,7 +10,7 @@ import (
"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 c COINUT

View File

@@ -14,7 +14,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
const coinutWebsocketURL = "wss://wsapi.coinut.com"
@@ -257,18 +258,18 @@ func (c *COINUT) WsSetInstrumentList() error {
// WsProcessOrderbookSnapshot processes the orderbook snapshot
func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
var bids []orderbook.Item
for _, bid := range ob.Buy {
for i := range ob.Buy {
bids = append(bids, orderbook.Item{
Amount: bid.Volume,
Price: bid.Price,
Amount: ob.Buy[i].Volume,
Price: ob.Buy[i].Price,
})
}
var asks []orderbook.Item
for _, ask := range ob.Sell {
for i := range ob.Sell {
asks = append(asks, orderbook.Item{
Amount: ask.Volume,
Price: ask.Price,
Amount: ob.Sell[i].Volume,
Price: ob.Sell[i].Price,
})
}
@@ -277,32 +278,24 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
newOrderBook.Bids = bids
newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID])
newOrderBook.AssetType = asset.Spot
newOrderBook.LastUpdated = time.Now()
return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, c.GetName(), false)
return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
}
// WsProcessOrderbookUpdate process an orderbook update
func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error {
p := currency.NewPairFromString(instrumentListByCode[ob.InstID])
if ob.Side == exchange.BuyOrderSide.ToLower().ToString() {
return c.Websocket.Orderbook.Update([]orderbook.Item{
{Price: ob.Price, Amount: ob.Volume}},
nil,
p,
time.Now(),
c.GetName(),
asset.Spot)
func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error {
p := currency.NewPairFromString(instrumentListByCode[update.InstID])
bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{
CurrencyPair: p,
UpdateID: update.TransID,
AssetType: asset.Spot,
}
return c.Websocket.Orderbook.Update([]orderbook.Item{
{Price: ob.Price, Amount: ob.Volume}},
nil,
p,
time.Now(),
c.GetName(),
asset.Spot)
if strings.EqualFold(update.Side, exchange.BuyOrderSide.ToLower().ToString()) {
bufferUpdate.Bids = []orderbook.Item{{Price: update.Price, Amount: update.Volume}}
} else {
bufferUpdate.Asks = []orderbook.Item{{Price: update.Price, Amount: update.Volume}}
}
return c.Websocket.Orderbook.Update(bufferUpdate)
}
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -99,6 +99,7 @@ func (c *COINUT) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets the current exchange configuration
@@ -134,6 +135,14 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
c.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
true,
false,
exch.Name)
return nil
}

View File

@@ -26,6 +26,8 @@ const (
DefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
// DefaultWebsocketResponseMaxLimit is the default max wait for an expected websocket response before a timeout
DefaultWebsocketResponseMaxLimit = time.Second * 7
// DefaultWebsocketOrderbookBufferLimit is the maximum number of orderbook updates that get stored before being applied
DefaultWebsocketOrderbookBufferLimit = 5
)
func (e *Base) checkAndInitRequester() {

View File

@@ -11,7 +11,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
const (

View File

@@ -6,7 +6,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
// Endpoint authentication types
@@ -313,6 +313,7 @@ type Base struct {
HTTPDebugging bool
WebsocketResponseCheckTimeout time.Duration
WebsocketResponseMaxLimit time.Duration
WebsocketOrderbookBufferLimit int64
Websocket *wshandler.Websocket
*request.Requester
Config *config.ExchangeConfig

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)

View File

@@ -13,7 +13,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
const (

View File

@@ -10,7 +10,7 @@ import (
"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"
)
// Please supply your own APIKEYS here for due diligence testing

View File

@@ -15,7 +15,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -161,15 +162,15 @@ func (g *Gateio) WsHandleData() {
continue
}
for _, trade := range trades {
for i := range trades {
g.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Now(),
CurrencyPair: currency.NewPairFromString(c),
AssetType: asset.Spot,
Exchange: g.GetName(),
Price: trade.Price,
Amount: trade.Amount,
Side: trade.Type,
Price: trades[i].Price,
Amount: trades[i].Amount,
Side: trades[i].Type,
}
}
@@ -198,9 +199,9 @@ func (g *Gateio) WsHandleData() {
var asks, bids []orderbook.Item
askData, askOk := data["asks"]
for _, ask := range askData {
amount, _ := strconv.ParseFloat(ask[1], 64)
price, _ := strconv.ParseFloat(ask[0], 64)
for i := range askData {
amount, _ := strconv.ParseFloat(askData[i][1], 64)
price, _ := strconv.ParseFloat(askData[i][0], 64)
asks = append(asks, orderbook.Item{
Amount: amount,
Price: price,
@@ -208,9 +209,9 @@ func (g *Gateio) WsHandleData() {
}
bidData, bidOk := data["bids"]
for _, bid := range bidData {
amount, _ := strconv.ParseFloat(bid[1], 64)
price, _ := strconv.ParseFloat(bid[0], 64)
for i := range bidData {
amount, _ := strconv.ParseFloat(bidData[i][1], 64)
price, _ := strconv.ParseFloat(bidData[i][0], 64)
bids = append(bids, orderbook.Item{
Amount: amount,
Price: price,
@@ -234,22 +235,22 @@ func (g *Gateio) WsHandleData() {
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.AssetType = asset.Spot
newOrderBook.LastUpdated = time.Now()
newOrderBook.Pair = currency.NewPairFromString(c)
err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook,
g.GetName(),
false)
if err != nil {
g.Websocket.DataHandler <- err
}
} else {
err = g.Websocket.Orderbook.Update(asks,
bids,
currency.NewPairFromString(c),
time.Now(),
g.GetName(),
asset.Spot)
err = g.Websocket.Orderbook.Update(
&wsorderbook.WebsocketOrderbookUpdate{
Asks: asks,
Bids: bids,
CurrencyPair: currency.NewPairFromString(c),
UpdateTime: time.Now(),
AssetType: asset.Spot,
})
if err != nil {
g.Websocket.DataHandler <- err
}
@@ -337,8 +338,8 @@ func (g *Gateio) GenerateDefaultSubscriptions() {
// Subscribe sends a websocket message to receive data from the channel
func (g *Gateio) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
params := []interface{}{channelToSubscribe.Currency.String()}
for _, paramValue := range channelToSubscribe.Params {
params = append(params, paramValue)
for i := range channelToSubscribe.Params {
params = append(params, channelToSubscribe.Params[i])
}
subscribe := WebsocketRequest{

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -102,6 +102,7 @@ func (g *Gateio) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets user configuration
@@ -138,6 +139,14 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) error {
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
RateLimit: gateioWebsocketRateLimit,
}
g.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
false,
false,
false,
exch.Name)
return nil
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -11,7 +11,7 @@ import (
"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"
)
// Please enter sandbox API keys & assigned roles for better testing procedures

View File

@@ -212,7 +212,7 @@ type WsMarketUpdateResponse struct {
// Event defines orderbook and trade data
type Event struct {
Type string `json:"change"`
Type string `json:"type"`
Reason string `json:"reason"`
Price float64 `json:"price,string"`
Delta float64 `json:"delta,string"`

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/gorilla/websocket"
@@ -16,7 +17,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -113,9 +115,11 @@ func (g *Gemini) WsSecureSubscribe(dialer *websocket.Dialer, url string) error {
headers.Add("Cache-Control", "no-cache")
g.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
ExchangeName: g.Name,
URL: endpoint,
Verbose: g.Verbose,
ExchangeName: g.Name,
URL: endpoint,
Verbose: g.Verbose,
ResponseCheckTimeout: responseCheckTimeout,
ResponseMaxLimit: responseMaxLimit,
}
err = g.AuthenticatedWebsocketConn.Dial(dialer, headers)
if err != nil {
@@ -256,84 +260,73 @@ func (g *Gemini) WsHandleData() {
func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pair) {
if result.Timestamp == 0 && result.TimestampMS == 0 {
var bids, asks []orderbook.Item
for _, event := range result.Events {
if event.Reason != "initial" {
for i := range result.Events {
if result.Events[i].Reason != "initial" {
g.Websocket.DataHandler <- errors.New("gemini_websocket.go orderbook should be snapshot only")
continue
}
if event.Side == "ask" {
if result.Events[i].Side == "ask" {
asks = append(asks, orderbook.Item{
Amount: event.Remaining,
Price: event.Price,
Amount: result.Events[i].Remaining,
Price: result.Events[i].Price,
})
} else {
bids = append(bids, orderbook.Item{
Amount: event.Remaining,
Price: event.Price,
Amount: result.Events[i].Remaining,
Price: result.Events[i].Price,
})
}
}
var newOrderBook orderbook.Base
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.AssetType = asset.Spot
newOrderBook.Pair = pair
err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook,
g.GetName(),
false)
if err != nil {
g.Websocket.DataHandler <- err
return
}
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair,
Asset: asset.Spot,
Exchange: g.GetName()}
} else {
for _, event := range result.Events {
if event.Type == "trade" {
var asks, bids []orderbook.Item
for i := 0; i < len(result.Events); i++ {
if result.Events[i].Type == "trade" {
g.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Now(),
CurrencyPair: pair,
AssetType: asset.Spot,
Exchange: g.Name,
EventTime: result.Timestamp,
Price: event.Price,
Amount: event.Amount,
Side: event.MakerSide,
Price: result.Events[i].Price,
Amount: result.Events[i].Amount,
Side: result.Events[i].MakerSide,
}
} else {
var i orderbook.Item
i.Amount = event.Remaining
i.Price = event.Price
if event.Side == "ask" {
err := g.Websocket.Orderbook.Update(nil,
[]orderbook.Item{i},
pair,
time.Now(),
g.GetName(),
asset.Spot)
if err != nil {
g.Websocket.DataHandler <- err
}
item := orderbook.Item{
Amount: result.Events[i].Remaining,
Price: result.Events[i].Price,
}
if strings.EqualFold(result.Events[i].Side, exchange.AskOrderSide.ToString()) {
asks = append(asks, item)
} else {
err := g.Websocket.Orderbook.Update([]orderbook.Item{i},
nil,
pair,
time.Now(),
g.GetName(),
asset.Spot)
if err != nil {
g.Websocket.DataHandler <- err
}
bids = append(bids, item)
}
}
}
err := g.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
Asks: asks,
Bids: bids,
CurrencyPair: pair,
UpdateTime: time.Unix(0, result.TimestampMS),
AssetType: asset.Spot,
})
if err != nil {
g.Websocket.DataHandler <- fmt.Errorf("%v %v", g.Name, err)
}
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair,
Asset: asset.Spot,
Exchange: g.GetName()}

View File

@@ -17,7 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -97,6 +97,7 @@ func (g *Gemini) SetDefaults() {
wshandler.WebsocketSequenceNumberSupported
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets exchange configuration parameters
@@ -136,6 +137,14 @@ func (g *Gemini) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
g.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
false,
false,
exch.Name)
return nil
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
const (

View File

@@ -11,7 +11,7 @@ import (
"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 h HitBTC

View File

@@ -15,7 +15,8 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/nonce"
"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"
)
@@ -227,13 +228,13 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
}
var bids []orderbook.Item
for _, bid := range ob.Params.Bid {
bids = append(bids, orderbook.Item{Amount: bid.Size, Price: bid.Price})
for i := range ob.Params.Bid {
bids = append(bids, orderbook.Item{Amount: ob.Params.Bid[i].Size, Price: ob.Params.Bid[i].Price})
}
var asks []orderbook.Item
for _, ask := range ob.Params.Ask {
asks = append(asks, orderbook.Item{Amount: ask.Size, Price: ask.Price})
for i := range ob.Params.Ask {
asks = append(asks, orderbook.Item{Amount: ob.Params.Ask[i].Size, Price: ob.Params.Ask[i].Price})
}
p := currency.NewPairFromString(ob.Params.Symbol)
@@ -242,10 +243,9 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.AssetType = asset.Spot
newOrderBook.LastUpdated = time.Now()
newOrderBook.Pair = p
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false)
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
if err != nil {
return err
}
@@ -260,23 +260,28 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
}
// WsProcessOrderbookUpdate updates a local cache
func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error {
if len(ob.Params.Bid) == 0 && len(ob.Params.Ask) == 0 {
func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error {
if len(update.Params.Bid) == 0 && len(update.Params.Ask) == 0 {
return errors.New("hitbtc_websocket.go error - no data")
}
var bids, asks []orderbook.Item
for _, bid := range ob.Params.Bid {
bids = append(bids, orderbook.Item{Price: bid.Price, Amount: bid.Size})
for i := range update.Params.Bid {
bids = append(bids, orderbook.Item{Price: update.Params.Bid[i].Price, Amount: update.Params.Bid[i].Size})
}
for _, ask := range ob.Params.Ask {
asks = append(asks, orderbook.Item{Price: ask.Price, Amount: ask.Size})
for i := range update.Params.Ask {
asks = append(asks, orderbook.Item{Price: update.Params.Ask[i].Price, Amount: update.Params.Ask[i].Size})
}
p := currency.NewPairFromString(ob.Params.Symbol)
err := h.Websocket.Orderbook.Update(bids, asks, p, time.Now(), h.GetName(), asset.Spot)
p := currency.NewPairFromString(update.Params.Symbol)
err := h.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
Asks: asks,
Bids: bids,
CurrencyPair: p,
UpdateID: update.Params.Sequence,
AssetType: asset.Spot,
})
if err != nil {
return err
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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,6 +100,7 @@ func (h *HitBTC) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets user exchange configuration settings
@@ -136,6 +137,14 @@ func (h *HitBTC) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
h.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
true,
false,
exch.Name)
return nil
}

View File

@@ -20,7 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
const (

View File

@@ -17,7 +17,7 @@ import (
"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"
)
// Please supply you own test keys here for due diligence testing.

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -245,7 +245,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
h.Websocket.DataHandler <- wshandler.KlineData{
Timestamp: time.Unix(0, kline.Timestamp),
Exchange: h.GetName(),
AssetType: "SPOT",
AssetType: asset.Spot,
Pair: currency.NewPairFromString(data[1]),
OpenPrice: kline.Tick.Open,
ClosePrice: kline.Tick.Close,
@@ -271,33 +271,27 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
}
// WsProcessOrderbook processes new orderbook data
func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error {
var bids []orderbook.Item
for _, data := range ob.Tick.Bids {
bidLevel := data.([]interface{})
func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error {
p := currency.NewPairFromString(symbol)
var bids, asks []orderbook.Item
for i := 0; i < len(update.Tick.Bids); i++ {
bidLevel := update.Tick.Bids[i].([]interface{})
bids = append(bids, orderbook.Item{Price: bidLevel[0].(float64),
Amount: bidLevel[0].(float64)})
}
var asks []orderbook.Item
for _, data := range ob.Tick.Asks {
askLevel := data.([]interface{})
for i := 0; i < len(update.Tick.Asks); i++ {
askLevel := update.Tick.Asks[i].([]interface{})
asks = append(asks, orderbook.Item{Price: askLevel[0].(float64),
Amount: askLevel[0].(float64)})
}
p := currency.NewPairFromString(symbol)
var newOrderBook orderbook.Base
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.Pair = p
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false)
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true)
if err != nil {
return err
}
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Exchange: h.GetName(),

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -101,6 +101,7 @@ func (h *HUOBI) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets user configuration
@@ -149,6 +150,14 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
h.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
false,
false,
false,
false,
exch.Name)
return nil
}

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -11,7 +11,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
// Please supply your own APIKEYS here for due diligence testing

View File

@@ -15,7 +15,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -272,33 +272,27 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) {
}
// WsProcessOrderbook processes new orderbook data
func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error {
var bids []orderbook.Item
for _, data := range ob.Tick.Bids {
bidLevel := data.([]interface{})
func (h *HUOBIHADAX) WsProcessOrderbook(update *WsDepth, symbol string) error {
p := currency.NewPairFromString(symbol)
var bids, asks []orderbook.Item
for i := 0; i < len(update.Tick.Bids); i++ {
bidLevel := update.Tick.Bids[i].([]interface{})
bids = append(bids, orderbook.Item{Price: bidLevel[0].(float64),
Amount: bidLevel[0].(float64)})
}
var asks []orderbook.Item
for _, data := range ob.Tick.Asks {
askLevel := data.([]interface{})
for i := 0; i < len(update.Tick.Asks); i++ {
askLevel := update.Tick.Asks[i].([]interface{})
asks = append(asks, orderbook.Item{Price: askLevel[0].(float64),
Amount: askLevel[0].(float64)})
}
p := currency.NewPairFromString(symbol)
var newOrderBook orderbook.Base
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.Pair = p
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false)
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true)
if err != nil {
return err
}
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Pair: p,
Exchange: h.GetName(),

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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,6 +100,7 @@ func (h *HUOBIHADAX) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets user configuration
@@ -145,6 +146,14 @@ func (h *HUOBIHADAX) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
h.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
false,
false,
false,
false,
exch.Name)
return nil
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
// IBotExchange enforces standard functions for all exchanges supported in

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)

View File

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

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
@@ -657,107 +654,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,9 +5,7 @@ import (
"fmt"
"math"
"net/http"
"sort"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
@@ -15,7 +13,8 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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,22 +39,13 @@ const (
krakenWsTrade = "trade"
krakenWsSpread = "spread"
krakenWsOrderbook = "book"
// Only supported asset type
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
@@ -229,22 +219,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{
@@ -349,16 +323,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)
}
}
}
@@ -366,7 +337,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: asset.Spot,
}
@@ -378,11 +349,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)))
@@ -390,17 +360,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)))
@@ -408,33 +376,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: asset.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{
// 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: asset.Spot,
ExchangeName: k.Name,
Pair: channelData.Pair,
CurrencyPair: channelData.Pair,
}
var highestLastUpdate time.Time
// Ask data is not always sent
if _, ok := obData["a"]; ok {
@@ -443,11 +403,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)))
@@ -463,7 +422,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,
})
@@ -475,201 +434,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(log.ExchangeSys,
"%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(log.ExchangeSys, "%v Current orderbook 'LastUpdated': %v",
k.Name,
krakenOrderBooks[channelData.ChannelID].LastUpdated)
}
lowestLastUpdated := orderbookBuffer[channelData.ChannelID][0].LastUpdated
if k.Verbose {
log.Debugf(log.ExchangeSys,
"%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(log.ExchangeSys,
"%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(log.ExchangeSys, "%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: asset.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(log.ExchangeSys,
"%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(log.ExchangeSys,
"%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(log.ExchangeSys, "%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(log.ExchangeSys,
"%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(log.ExchangeSys,
"%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(log.ExchangeSys,
"%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

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -103,6 +103,7 @@ func (k *Kraken) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
k.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
k.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
k.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets current exchange configuration
@@ -139,6 +140,14 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
k.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
false,
false,
exch.Name)
return nil
}

View File

@@ -23,6 +23,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
### Current Features
+ REST Support
+ Websocket Support
### How to enable

View File

@@ -37,6 +37,7 @@ const (
// LakeBTC is the overarching type across the LakeBTC package
type LakeBTC struct {
exchange.Base
WebsocketConn
}
// GetTicker returns the current ticker from lakeBTC

View File

@@ -1,6 +1,7 @@
package lakebtc
import (
"fmt"
"testing"
"github.com/thrasher-corp/gocryptotrader/common"
@@ -8,9 +9,12 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
var l LakeBTC
var setupRan bool
// Please add your own APIkeys to do correct due diligence testing.
const (
@@ -20,21 +24,27 @@ const (
)
func TestSetDefaults(t *testing.T) {
l.SetDefaults()
if !setupRan {
l.SetDefaults()
}
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC")
if err != nil {
t.Error("Test Failed - LakeBTC Setup() init error")
if !setupRan {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC")
if err != nil {
t.Error("Test Failed - LakeBTC Setup() init error")
}
lakebtcConfig.API.AuthenticatedSupport = true
lakebtcConfig.API.Credentials.Key = apiKey
lakebtcConfig.API.Credentials.Secret = apiSecret
lakebtcConfig.Features.Enabled.Websocket = true
l.Setup(lakebtcConfig)
l.API.Endpoints.WebsocketURL = lakeBTCWSURL
setupRan = true
}
lakebtcConfig.API.AuthenticatedSupport = true
lakebtcConfig.API.Credentials.Key = apiKey
lakebtcConfig.API.Credentials.Secret = apiSecret
l.Setup(lakebtcConfig)
}
func TestFetchTradablePairs(t *testing.T) {
@@ -448,3 +458,65 @@ func TestGetDepositAddress(t *testing.T) {
}
}
}
// TestWsConn websocket connection test
func TestWsConn(t *testing.T) {
TestSetDefaults(t)
TestSetup(t)
if !l.Websocket.IsEnabled() {
t.Skip(wshandler.WebsocketNotEnabled)
}
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
err := l.WsConnect()
if err != nil {
t.Fatal(err)
}
}
// TestWsTradeProcessing logic test
func TestWsTradeProcessing(t *testing.T) {
TestSetDefaults(t)
TestSetup(t)
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
json := `{"trades":[{"type":"sell","date":1564985787,"price":"11913.02","amount":"0.49"}]}`
err := l.processTrades(json, "market-btcusd-global")
if err != nil {
t.Error(err)
}
}
// TestWsTickerProcessing logic test
func TestWsTickerProcessing(t *testing.T) {
TestSetDefaults(t)
TestSetup(t)
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
json := `{"btcusd":{"low":"10990.05","high":"11966.24","last":"11903.29","volume":"1803.967079","sell":"11912.39","buy":"11902.2"},"btceur":{"low":"9886.87","high":"10732.72","last":"10691.44","volume":"87.994478","sell":"10711.62","buy":"10691.44"},"btchkd":{"low":null,"high":null,"last":"51776.98","volume":null,"sell":"93307.37","buy":"93177.56"},"btcjpy":{"low":"1176039.0","high":"1272246.0","last":"1265680.0","volume":"129.021421","sell":"1266764.0","buy":"1265680.0"},"btcgbp":{"low":"9157.12","high":"9953.43","last":"9941.28","volume":"10.4997","sell":"10007.89","buy":"9941.28"},"btcaud":{"low":"16102.57","high":"17594.22","last":"17548.16","volume":"7.338316","sell":"17616.67","buy":"17549.69"},"btccad":{"low":"14541.69","high":"15834.87","last":"15763.54","volume":"30.480309","sell":"15793.45","buy":"15756.13"},"btcsgd":{"low":"15133.82","high":"16501.62","last":"16455.53","volume":"4.044026","sell":"16484.37","buy":"16462.18"},"btcchf":{"low":"10800.58","high":"11526.24","last":"11526.24","volume":"0.1765","sell":"11675.34","buy":"11632.02"},"btcnzd":{"low":null,"high":null,"last":"8340.98","volume":null,"sell":"18315.49","buy":"18221.37"},"btcngn":{"low":null,"high":null,"last":"600000.0","volume":null,"sell":null,"buy":null},"eurusd":{"low":"1.1088","high":"1.1138","last":"1.1125","volume":"2680.105249","sell":"1.1142","buy":"1.1121"},"gbpusd":{"low":"1.1934","high":"1.1958","last":"1.1934","volume":"1493.923823","sell":"1.1979","buy":"1.1903"},"usdjpy":{"low":"105.26","high":"107.25","last":"106.33","volume":"114490.2179","sell":"106.34","buy":"106.27"},"usdhkd":{"low":null,"high":null,"last":"7.851","volume":null,"sell":"7.8328","buy":"7.8286"},"usdcad":{"low":"1.3225","high":"1.3272","last":"1.3255","volume":"11033.9877","sell":"1.3258","buy":"1.3238"},"usdsgd":{"low":"1.3776","high":"1.3839","last":"1.3838","volume":"2523.75","sell":"1.3838","buy":"1.3819"},"audusd":{"low":"0.6764","high":"0.6853","last":"0.6771","volume":"5442.608321","sell":"0.6782","buy":"0.6762"},"nzdusd":{"low":null,"high":null,"last":"0.6758","volume":null,"sell":"0.6532","buy":"0.6504"},"usdchf":{"low":"0.9838","high":"0.9838","last":"0.9838","volume":"108.3352","sell":"0.9801","buy":"0.9773"},"usdngn":{"low":null,"high":null,"last":"200.0","volume":null,"sell":null,"buy":null},"ethbtc":{"low":"0.0205","high":"0.025","last":"0.0205","volume":null,"sell":"0.03","buy":"0.0194"},"ltcbtc":{"low":null,"high":null,"last":"0.0114","volume":null,"sell":"0.009","buy":"0.0073"},"bchbtc":{"low":null,"high":null,"last":"0.0544","volume":null,"sell":"0.0322","buy":"0.0274"},"xrpbtc":{"low":"0.000042","high":"0.000042","last":"0.000042","volume":null,"sell":"0.000037","buy":"0.000022"},"baceth":{"low":"0.000035","high":"0.000035","last":"0.000035","volume":null,"sell":"0.0015","buy":null}}`
err := l.processTicker(json)
if err != nil {
t.Error(err)
}
}
func TestGetCurrencyFromChannel(t *testing.T) {
curr := currency.NewPair(currency.LTC, currency.BTC)
result := l.getCurrencyFromChannel(fmt.Sprintf("%v%v%v", marketSubstring, curr, globalSubstring))
if curr != result {
t.Errorf("currency result is not equal. Expected %v", curr)
}
}
// TestWsOrderbookProcessing logic test
func TestWsOrderbookProcessing(t *testing.T) {
TestSetDefaults(t)
TestSetup(t)
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
json := `{"asks":[["11905.66","0.0019"],["11905.73","0.0015"],["11906.43","0.0013"],["11906.62","0.0019"],["11907.25","11.087"],["11907.66","0.0006"],["11907.73","0.3113"],["11907.84","0.0006"],["11908.37","0.0016"],["11908.86","10.3786"],["11909.54","4.2955"],["11910.15","0.0012"],["11910.56","13.5505"],["11911.06","0.0011"],["11911.37","0.0023"]],"bids":[["11905.55","0.0171"],["11904.43","0.0225"],["11903.31","0.0223"],["11902.2","0.0027"],["11901.92","1.002"],["11901.6","0.0015"],["11901.49","0.0012"],["11901.08","0.0227"],["11900.93","0.0009"],["11900.53","1.662"],["11900.08","0.001"],["11900.01","3.6745"],["11899.96","0.003"],["11899.91","0.0006"],["11899.44","0.0013"]]}`
err := l.processOrderbook(json, "market-btcusd-global")
if err != nil {
t.Error(err)
}
}

View File

@@ -1,5 +1,7 @@
package lakebtc
import pusher "github.com/toorop/go-pusher"
// Ticker holds ticker information
type Ticker struct {
Last float64
@@ -112,3 +114,30 @@ type Withdraw struct {
At int64 `json:"at"`
Error string `json:"error"`
}
// WebsocketConn defines a pusher websocket connection
type WebsocketConn struct {
Client *pusher.Client
Ticker chan *pusher.Event
Orderbook chan *pusher.Event
Trade chan *pusher.Event
}
// WsOrderbookUpdate contains orderbook data from websocket
type WsOrderbookUpdate struct {
Asks [][]string `json:"asks"`
Bids [][]string `json:"bids"`
}
// WsTrades contains trade data from websocket
type WsTrades struct {
Trades []WsTrade `json:"trades"`
}
// WsTrade contains individual trade details from websocket
type WsTrade struct {
Type string `json:"type"`
Date int64 `json:"date"`
Price float64 `json:"price,string"`
Amount float64 `json:"amount,string"`
}

View File

@@ -0,0 +1,252 @@
package lakebtc
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
"github.com/toorop/go-pusher"
)
const (
lakeBTCWSURL = "ws.lakebtc.com:8085"
marketGlobalEndpoint = "market-global"
marketSubstring = "market-"
globalSubstring = "-global"
volumeString = "volume"
highString = "high"
lowString = "low"
wssSchem = "wss"
)
// WsConnect initiates a new websocket connection
func (l *LakeBTC) WsConnect() error {
if !l.Websocket.IsEnabled() || !l.IsEnabled() {
return errors.New(wshandler.WebsocketNotEnabled)
}
var err error
l.WebsocketConn.Client, err = pusher.NewCustomClient(strings.ToLower(l.Name), lakeBTCWSURL, wssSchem)
if err != nil {
return err
}
err = l.WebsocketConn.Client.Subscribe(marketGlobalEndpoint)
if err != nil {
return err
}
l.GenerateDefaultSubscriptions()
err = l.listenToEndpoints()
if err != nil {
return err
}
go l.wsHandleIncomingData()
return nil
}
func (l *LakeBTC) listenToEndpoints() error {
var err error
l.WebsocketConn.Ticker, err = l.WebsocketConn.Client.Bind("tickers")
if err != nil {
return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err)
}
l.WebsocketConn.Orderbook, err = l.WebsocketConn.Client.Bind("update")
if err != nil {
return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err)
}
l.WebsocketConn.Trade, err = l.WebsocketConn.Client.Bind("trades")
if err != nil {
return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err)
}
return nil
}
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (l *LakeBTC) GenerateDefaultSubscriptions() {
var subscriptions []wshandler.WebsocketChannelSubscription
enabledCurrencies := l.GetEnabledPairs(asset.Spot)
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = ""
channel := fmt.Sprintf("%v%v%v", marketSubstring, enabledCurrencies[j].Lower(), globalSubstring)
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
Channel: channel,
Currency: enabledCurrencies[j],
})
}
l.Websocket.SubscribeToChannels(subscriptions)
}
// Subscribe sends a websocket message to receive data from the channel
func (l *LakeBTC) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
return l.WebsocketConn.Client.Subscribe(channelToSubscribe.Channel)
}
// wsHandleIncomingData services incoming data from the websocket connection
func (l *LakeBTC) wsHandleIncomingData() {
l.Websocket.Wg.Add(1)
defer l.Websocket.Wg.Done()
for {
select {
case <-l.Websocket.ShutdownC:
return
case data := <-l.WebsocketConn.Ticker:
if l.Verbose {
log.Debugf(log.ExchangeSys,
"%v Websocket message received: %v", l.Name, data)
}
l.Websocket.TrafficAlert <- struct{}{}
err := l.processTicker(data.Data)
if err != nil {
l.Websocket.DataHandler <- err
return
}
case data := <-l.WebsocketConn.Trade:
l.Websocket.TrafficAlert <- struct{}{}
if l.Verbose {
log.Debugf(log.ExchangeSys,
"%v Websocket message received: %v", l.Name, data)
}
err := l.processTrades(data.Data, data.Channel)
if err != nil {
l.Websocket.DataHandler <- err
return
}
case data := <-l.WebsocketConn.Orderbook:
l.Websocket.TrafficAlert <- struct{}{}
if l.Verbose {
log.Debugf(log.ExchangeSys,
"%v Websocket message received: %v", l.Name, data)
}
err := l.processOrderbook(data.Data, data.Channel)
if err != nil {
l.Websocket.DataHandler <- err
return
}
}
}
}
func (l *LakeBTC) processTrades(data, channel string) error {
var tradeData WsTrades
err := common.JSONDecode([]byte(data), &tradeData)
if err != nil {
return err
}
curr := l.getCurrencyFromChannel(channel)
for i := 0; i < len(tradeData.Trades); i++ {
l.Websocket.DataHandler <- wshandler.TradeData{
Timestamp: time.Unix(tradeData.Trades[i].Date, 0),
CurrencyPair: curr,
AssetType: asset.Spot,
Exchange: l.GetName(),
EventType: asset.Spot.String(),
EventTime: tradeData.Trades[i].Date,
Price: tradeData.Trades[i].Price,
Amount: tradeData.Trades[i].Amount,
Side: tradeData.Trades[i].Type,
}
}
return nil
}
func (l *LakeBTC) processOrderbook(obUpdate, channel string) error {
var update WsOrderbookUpdate
err := common.JSONDecode([]byte(obUpdate), &update)
if err != nil {
return err
}
book := orderbook.Base{
Pair: l.getCurrencyFromChannel(channel),
LastUpdated: time.Now(),
AssetType: asset.Spot,
ExchangeName: l.Name,
}
for i := 0; i < len(update.Asks); i++ {
var amount, price float64
amount, err = strconv.ParseFloat(update.Asks[i][1], 64)
if err != nil {
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, update.Asks[i])
continue
}
price, err = strconv.ParseFloat(update.Asks[i][0], 64)
if err != nil {
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing orderbook price %v", l.Name, update.Asks[i])
continue
}
book.Asks = append(book.Asks, orderbook.Item{
Amount: amount,
Price: price,
})
}
for i := 0; i < len(update.Bids); i++ {
var amount, price float64
amount, err = strconv.ParseFloat(update.Bids[i][1], 64)
if err != nil {
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, update.Bids[i])
continue
}
price, err = strconv.ParseFloat(update.Bids[i][0], 64)
if err != nil {
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing orderbook price %v", l.Name, update.Bids[i])
continue
}
book.Bids = append(book.Bids, orderbook.Item{
Amount: amount,
Price: price,
})
}
return l.Websocket.Orderbook.LoadSnapshot(&book, true)
}
func (l *LakeBTC) getCurrencyFromChannel(channel string) currency.Pair {
curr := strings.Replace(channel, marketSubstring, "", 1)
curr = strings.Replace(curr, globalSubstring, "", 1)
return currency.NewPairFromString(curr)
}
func (l *LakeBTC) processTicker(ticker string) error {
var tUpdate map[string]interface{}
err := common.JSONDecode([]byte(ticker), &tUpdate)
if err != nil {
l.Websocket.DataHandler <- err
return err
}
for k, v := range tUpdate {
tickerData := v.(map[string]interface{})
if tickerData[highString] == nil || tickerData[lowString] == nil || tickerData[volumeString] == nil {
continue
}
high, err := strconv.ParseFloat(tickerData[highString].(string), 64)
if err != nil {
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'high' %v", l.Name, tickerData)
continue
}
low, err := strconv.ParseFloat(tickerData[lowString].(string), 64)
if err != nil {
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, tickerData)
continue
}
vol, err := strconv.ParseFloat(tickerData[volumeString].(string), 64)
if err != nil {
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'volume' %v", l.Name, tickerData)
continue
}
l.Websocket.DataHandler <- wshandler.TickerData{
Timestamp: time.Now(),
Pair: currency.NewPairFromString(k),
AssetType: asset.Spot,
Exchange: l.GetName(),
Quantity: vol,
HighPrice: high,
LowPrice: low,
}
}
return nil
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -68,7 +68,7 @@ func (l *LakeBTC) SetDefaults() {
l.Features = exchange.Features{
Supports: exchange.FeaturesSupported{
REST: true,
Websocket: false,
Websocket: true,
RESTCapabilities: exchange.ProtocolFeatures{
AutoPairUpdates: true,
TickerBatching: true,
@@ -88,6 +88,14 @@ func (l *LakeBTC) SetDefaults() {
l.API.Endpoints.URLDefault = lakeBTCAPIURL
l.API.Endpoints.URL = l.API.Endpoints.URLDefault
l.Websocket = wshandler.New()
l.API.Endpoints.WebsocketURL = lakeBTCWSURL
l.Websocket.Functionality = wshandler.WebsocketOrderbookSupported |
wshandler.WebsocketTradeDataSupported |
wshandler.WebsocketSubscribeSupported
l.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
l.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
l.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets exchange configuration profile
@@ -97,7 +105,32 @@ func (l *LakeBTC) Setup(exch *config.ExchangeConfig) error {
return nil
}
return l.SetupDefaults(exch)
err := l.SetupDefaults(exch)
if err != nil {
return err
}
err = l.Websocket.Setup(l.WsConnect,
l.Subscribe,
nil,
exch.Name,
exch.Features.Enabled.Websocket,
exch.Verbose,
lakeBTCWSURL,
exch.API.Endpoints.WebsocketURL,
exch.API.AuthenticatedWebsocketSupport)
if err != nil {
return err
}
l.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
false,
false,
false,
false,
exch.Name)
return nil
}
// Start starts the LakeBTC go routine
@@ -373,8 +406,7 @@ func (l *LakeBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange
// GetWebsocket returns a pointer to the exchange websocket
func (l *LakeBTC) GetWebsocket() (*wshandler.Websocket, error) {
// Documents are too vague to implement
return nil, common.ErrFunctionNotSupported
return l.Websocket, nil
}
// GetFeeByType returns an estimate of fee based on type of transaction

View File

@@ -17,7 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
// Please supply you own test keys here for due diligence testing.

View File

@@ -10,7 +10,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -104,6 +104,7 @@ func (o *OKCoin) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Start starts the OKGroup go routine

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -104,6 +104,7 @@ func (o *OKEX) SetDefaults() {
wshandler.WebsocketMessageCorrelationSupported
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Start starts the OKGroup go routine

View File

@@ -16,7 +16,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
log "github.com/thrasher-corp/gocryptotrader/logger"
)

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"hash/crc32"
"net/http"
"sort"
"strconv"
"strings"
"sync"
@@ -18,7 +17,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -311,7 +311,7 @@ func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string {
// eg "spot/ticker:BTCUSD" results in "SPOT"
func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item {
assetIndex := strings.Index(table, "/")
return asset.Item(strings.ToUpper(table[:assetIndex]))
return asset.Item(table[:assetIndex])
}
// WsHandleDataResponse classifies the WS response and sends to appropriate handler
@@ -469,7 +469,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i
ExchangeName: o.GetName(),
}
err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook, o.GetName(), true)
err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true)
if err != nil {
return err
}
@@ -484,43 +484,29 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i
// WsProcessUpdateOrderbook updates an existing orderbook using websocket data
// After merging WS data, it will sort, validate and finally update the existing orderbook
func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, tableName string) error {
internalOrderbook, err := o.FetchOrderbook(instrument, o.GetAssetTypeFromTableName(tableName))
update := wsorderbook.WebsocketOrderbookUpdate{
AssetType: asset.Spot,
CurrencyPair: instrument,
UpdateTime: wsEventData.Timestamp,
}
update.Asks = o.AppendWsOrderbookItems(wsEventData.Asks)
update.Bids = o.AppendWsOrderbookItems(wsEventData.Bids)
err := o.Websocket.Orderbook.Update(&update)
if err != nil {
return errors.New("orderbook nil, could not load existing orderbook")
log.Error(log.ExchangeSys, err)
}
if internalOrderbook.LastUpdated.After(wsEventData.Timestamp) {
if o.Verbose {
log.Errorf(log.ExchangeSys, "Orderbook update out of order. Existing: %v, Attempted: %v", internalOrderbook.LastUpdated.Unix(), wsEventData.Timestamp.Unix())
}
return errors.New("updated orderbook is older than existing")
}
internalOrderbook.Asks = o.WsUpdateOrderbookEntry(wsEventData.Asks, internalOrderbook.Asks)
internalOrderbook.Bids = o.WsUpdateOrderbookEntry(wsEventData.Bids, internalOrderbook.Bids)
sort.Slice(internalOrderbook.Asks, func(i, j int) bool {
return internalOrderbook.Asks[i].Price < internalOrderbook.Asks[j].Price
})
sort.Slice(internalOrderbook.Bids, func(i, j int) bool {
return internalOrderbook.Bids[i].Price > internalOrderbook.Bids[j].Price
})
checksum := o.CalculateUpdateOrderbookChecksum(&internalOrderbook)
updatedOb := o.Websocket.Orderbook.GetOrderbook(instrument, asset.Spot)
checksum := o.CalculateUpdateOrderbookChecksum(updatedOb)
if checksum == wsEventData.Checksum {
if o.Verbose {
log.Debug(log.ExchangeSys, "Orderbook valid")
}
internalOrderbook.LastUpdated = wsEventData.Timestamp
if o.Verbose {
log.Debug(log.ExchangeSys, "Internalising orderbook")
}
err := o.Websocket.Orderbook.LoadSnapshot(&internalOrderbook, o.GetName(), true)
if err != nil {
log.Error(log.ExchangeSys, err)
}
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
Exchange: o.GetName(),
Asset: o.GetAssetTypeFromTableName(tableName),
Pair: instrument,
}
} else {
if o.Verbose {
log.Warnln(log.ExchangeSys, "Orderbook invalid")
@@ -530,35 +516,6 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in
return nil
}
// WsUpdateOrderbookEntry takes WS bid or ask data and merges it with existing orderbook bid or ask data
func (o *OKGroup) WsUpdateOrderbookEntry(wsEntries [][]interface{}, existingOrderbookEntries []orderbook.Item) []orderbook.Item {
for j := range wsEntries {
wsEntryPrice, _ := strconv.ParseFloat(wsEntries[j][0].(string), 64)
wsEntryAmount, _ := strconv.ParseFloat(wsEntries[j][1].(string), 64)
matchFound := false
for k := 0; k < len(existingOrderbookEntries); k++ {
if existingOrderbookEntries[k].Price != wsEntryPrice {
continue
}
matchFound = true
if wsEntryAmount == 0 {
existingOrderbookEntries = append(existingOrderbookEntries[:k], existingOrderbookEntries[k+1:]...)
k--
continue
}
existingOrderbookEntries[k].Amount = wsEntryAmount
continue
}
if !matchFound {
existingOrderbookEntries = append(existingOrderbookEntries, orderbook.Item{
Amount: wsEntryAmount,
Price: wsEntryPrice,
})
}
}
return existingOrderbookEntries
}
// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask entries from websocket data
// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them
// This will also work when there are less than 25 entries (for whatever reason)

View File

@@ -12,7 +12,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -54,6 +54,14 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
o.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
false,
false,
false,
false,
exch.Name)
return nil
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
)
const (

View File

@@ -11,7 +11,7 @@ import (
"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 p Poloniex

View File

@@ -15,7 +15,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"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"
)
@@ -157,7 +158,7 @@ func (p *Poloniex) WsHandleData() {
}
case "o":
currencyPair := currencyIDMap[chanID]
err := p.WsProcessOrderbookUpdate(dataL3, currencyPair)
err := p.WsProcessOrderbookUpdate(int64(data[1].(float64)), dataL3, currencyPair)
if err != nil {
p.Websocket.DataHandler <- err
continue
@@ -328,43 +329,34 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e
newOrderBook.Asks = asks
newOrderBook.Bids = bids
newOrderBook.AssetType = asset.Spot
newOrderBook.LastUpdated = time.Now()
newOrderBook.Pair = currency.NewPairFromString(symbol)
return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook, p.GetName(), false)
return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
}
// WsProcessOrderbookUpdate processses new orderbook updates
func (p *Poloniex) WsProcessOrderbookUpdate(target []interface{}, symbol string) error {
// WsProcessOrderbookUpdate processes new orderbook updates
func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []interface{}, symbol string) error {
sideCheck := target[1].(float64)
cP := currency.NewPairFromString(symbol)
price, err := strconv.ParseFloat(target[2].(string), 64)
if err != nil {
return err
}
volume, err := strconv.ParseFloat(target[3].(string), 64)
if err != nil {
return err
}
if sideCheck == 0 {
return p.Websocket.Orderbook.Update(nil,
[]orderbook.Item{{Price: price, Amount: volume}},
cP,
time.Now(),
p.GetName(),
asset.Spot)
update := &wsorderbook.WebsocketOrderbookUpdate{
CurrencyPair: cP,
AssetType: asset.Spot,
UpdateID: sequenceNumber,
}
return p.Websocket.Orderbook.Update([]orderbook.Item{{Price: price, Amount: volume}},
nil,
cP,
time.Now(),
p.GetName(),
asset.Spot)
if sideCheck == 0 {
update.Bids = []orderbook.Item{{Price: price, Amount: volume}}
} else {
update.Asks = []orderbook.Item{{Price: price, Amount: volume}}
}
return p.Websocket.Orderbook.Update(update)
}
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()

View File

@@ -15,7 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"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"
)
@@ -98,6 +98,7 @@ func (p *Poloniex) SetDefaults() {
wshandler.WebsocketAuthenticatedEndpointsSupported
p.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
p.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
p.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
}
// Setup sets user exchange configuration settings
@@ -133,6 +134,14 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) error {
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
}
p.Websocket.Orderbook.Setup(
exch.WebsocketOrderbookBufferLimit,
true,
true,
true,
false,
exch.Name)
return nil
}

View File

@@ -12,7 +12,7 @@ const (
WebsocketResponseExtendedTimeout = (15 * time.Second)
// WebsocketChannelOverrideCapacity used in websocket testing
// Defines channel capacity as defaults size can block tests
WebsocketChannelOverrideCapacity = 10
WebsocketChannelOverrideCapacity = 20
)
// GetWebsocketInterfaceChannelOverride returns a new interface based channel

View File

@@ -1,17 +1,21 @@
package wshandler
import (
"bytes"
"compress/flate"
"compress/gzip"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
@@ -97,15 +101,15 @@ func (w *Websocket) Connect() error {
go w.trafficMonitor(&anotherWG)
anotherWG.Wait()
if !w.connectionMonitorRunning {
go w.wsConnectionMonitor()
go w.connectionMonitor()
}
go w.manageSubscriptions()
return nil
}
// WsConnectionMonitor ensures that the WS keeps connecting
func (w *Websocket) wsConnectionMonitor() {
// connectionMonitor ensures that the WS keeps connecting
func (w *Websocket) connectionMonitor() {
w.m.Lock()
w.connectionMonitorRunning = true
w.m.Unlock()
@@ -118,13 +122,14 @@ func (w *Websocket) wsConnectionMonitor() {
w.m.Lock()
if !w.enabled {
w.m.Unlock()
w.DataHandler <- fmt.Errorf("%v WsConnectionMonitor: websocket disabled, shutting down", w.exchangeName)
w.DataHandler <- fmt.Errorf("%v connectionMonitor: websocket disabled, shutting down", w.exchangeName)
err := w.Shutdown()
if err != nil {
log.Error(log.WebsocketMgr, err)
}
if w.verbose {
log.Debugf(log.WebsocketMgr, "%v WsConnectionMonitor exiting", w.exchangeName)
log.Debugf(log.WebsocketMgr, "%v connectionMonitor exiting",
w.exchangeName)
}
return
}
@@ -200,7 +205,6 @@ func (w *Websocket) Shutdown() error {
if !w.connected && w.ShutdownC == nil {
return fmt.Errorf("%v cannot shutdown a disconnected websocket", w.exchangeName)
}
if w.verbose {
log.Debugf(log.WebsocketMgr, "%v shutting down websocket channels", w.exchangeName)
}
@@ -227,11 +231,11 @@ func (w *Websocket) Shutdown() error {
}
// WebsocketReset sends the shutdown command, waits for channel/func closure and then reconnects
func (w *Websocket) WebsocketReset() error {
func (w *Websocket) WebsocketReset() {
err := w.Shutdown()
if err != nil {
// does not return here to allow connection to be made if already shut down
log.Errorf(log.WebsocketMgr, "%v shutdown error: %v", w.exchangeName, err)
w.DataHandler <- fmt.Errorf("%v shutdown error: %v", w.exchangeName, err)
}
log.Infof(log.WebsocketMgr, "%v reconnecting to websocket", w.exchangeName)
w.m.Lock()
@@ -239,9 +243,8 @@ func (w *Websocket) WebsocketReset() error {
w.m.Unlock()
err = w.Connect()
if err != nil {
log.Errorf(log.WebsocketMgr, "%v connection error: %v", w.exchangeName, err)
w.DataHandler <- fmt.Errorf("%v connection error: %v", w.exchangeName, err)
}
return err
}
// trafficMonitor monitors traffic and switches connection modes for websocket
@@ -345,7 +348,6 @@ func (w *Websocket) SetWsStatusAndConnection(enabled bool) error {
enabled)
}
w.enabled = enabled
if !w.init {
if enabled {
if w.connected {
@@ -421,216 +423,6 @@ func (w *Websocket) GetName() string {
return w.exchangeName
}
// Update updates a local cache using bid targets and ask targets then updates
// main cache in orderbook.go
// Volume == 0; deletion at price target
// Price target not found; append of price target
// Price target found; amend volume of price target
func (w *WebsocketOrderbookLocal) Update(bidTargets, askTargets []orderbook.Item,
p currency.Pair,
updated time.Time,
exchName string, assetType asset.Item) error {
if bidTargets == nil && askTargets == nil {
return errors.New("exchange.go websocket orderbook cache Update() error - cannot have bids and ask targets both nil")
}
if w.lastUpdated.After(updated) {
return errors.New("exchange.go WebsocketOrderbookLocal Update() - update is before last update time")
}
w.m.Lock()
defer w.m.Unlock()
var orderbookAddress *orderbook.Base
for i := range w.ob {
if w.ob[i].Pair == p && w.ob[i].AssetType == assetType {
orderbookAddress = w.ob[i]
}
}
if orderbookAddress == nil {
return fmt.Errorf("exchange.go WebsocketOrderbookLocal Update() - orderbook.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
exchName,
p.String(),
assetType)
}
if len(orderbookAddress.Asks) == 0 || len(orderbookAddress.Bids) == 0 {
return errors.New("exchange.go websocket orderbook cache Update() error - snapshot incorrectly loaded")
}
if orderbookAddress.Pair == (currency.Pair{}) {
return fmt.Errorf("exchange.go websocket orderbook cache Update() error - snapshot not found %v",
p)
}
for x := range bidTargets {
// bid targets
func() {
for y := range orderbookAddress.Bids {
if orderbookAddress.Bids[y].Price == bidTargets[x].Price {
if bidTargets[x].Amount == 0 {
// Delete
orderbookAddress.Bids = append(orderbookAddress.Bids[:y],
orderbookAddress.Bids[y+1:]...)
return
}
// Amend
orderbookAddress.Bids[y].Amount = bidTargets[x].Amount
return
}
}
if bidTargets[x].Amount == 0 {
// Makes sure we dont append things we missed
return
}
// Append
orderbookAddress.Bids = append(orderbookAddress.Bids, orderbook.Item{
Price: bidTargets[x].Price,
Amount: bidTargets[x].Amount,
})
}()
// bid targets
}
for x := range askTargets {
func() {
for y := range orderbookAddress.Asks {
if orderbookAddress.Asks[y].Price == askTargets[x].Price {
if askTargets[x].Amount == 0 {
// Delete
orderbookAddress.Asks = append(orderbookAddress.Asks[:y],
orderbookAddress.Asks[y+1:]...)
return
}
// Amend
orderbookAddress.Asks[y].Amount = askTargets[x].Amount
return
}
}
if askTargets[x].Amount == 0 {
// Makes sure we dont append things we missed
return
}
// Append
orderbookAddress.Asks = append(orderbookAddress.Asks, orderbook.Item{
Price: askTargets[x].Price,
Amount: askTargets[x].Amount,
})
}()
}
return orderbookAddress.Process()
}
// LoadSnapshot loads initial snapshot of orderbook data, overite allows full
// orderbook to be completely rewritten because the exchange is a doing a full
// update not an incremental one
func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, exchName string, overwrite bool) error {
if len(newOrderbook.Asks) == 0 || len(newOrderbook.Bids) == 0 {
return errors.New("exchange.go websocket orderbook cache LoadSnapshot() error - snapshot ask and bids are nil")
}
w.m.Lock()
defer w.m.Unlock()
for i := range w.ob {
if w.ob[i].Pair.Equal(newOrderbook.Pair) && w.ob[i].AssetType == newOrderbook.AssetType {
if overwrite {
w.ob[i] = newOrderbook
return newOrderbook.Process()
}
return errors.New("exchange.go websocket orderbook cache LoadSnapshot() error - Snapshot instance already found")
}
}
w.ob = append(w.ob, newOrderbook)
return newOrderbook.Process()
}
// UpdateUsingID updates orderbooks using specified ID
func (w *WebsocketOrderbookLocal) UpdateUsingID(bidTargets, askTargets []orderbook.Item,
p currency.Pair,
exchName string, assetType asset.Item, action string) error {
w.m.Lock()
defer w.m.Unlock()
var orderbookAddress *orderbook.Base
for i := range w.ob {
if w.ob[i].Pair == p && w.ob[i].AssetType == assetType {
orderbookAddress = w.ob[i]
}
}
if orderbookAddress == nil {
return fmt.Errorf("exchange.go WebsocketOrderbookLocal Update() - orderbook.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
exchName,
assetType,
p.String())
}
switch action {
case "update":
for _, target := range bidTargets {
for i := range orderbookAddress.Bids {
if orderbookAddress.Bids[i].ID == target.ID {
orderbookAddress.Bids[i].Amount = target.Amount
break
}
}
}
for _, target := range askTargets {
for i := range orderbookAddress.Asks {
if orderbookAddress.Asks[i].ID == target.ID {
orderbookAddress.Asks[i].Amount = target.Amount
break
}
}
}
case "delete":
for _, target := range bidTargets {
for i := range orderbookAddress.Bids {
if orderbookAddress.Bids[i].ID == target.ID {
orderbookAddress.Bids = append(orderbookAddress.Bids[:i],
orderbookAddress.Bids[i+1:]...)
break
}
}
}
for _, target := range askTargets {
for i := range orderbookAddress.Asks {
if orderbookAddress.Asks[i].ID == target.ID {
orderbookAddress.Asks = append(orderbookAddress.Asks[:i],
orderbookAddress.Asks[i+1:]...)
break
}
}
}
case "insert":
orderbookAddress.Bids = append(orderbookAddress.Bids, bidTargets...)
orderbookAddress.Asks = append(orderbookAddress.Asks, askTargets...)
}
return orderbookAddress.Process()
}
// FlushCache flushes w.ob data to be garbage collected and refreshed when a
// connection is lost and reconnected
func (w *WebsocketOrderbookLocal) FlushCache() {
w.m.Lock()
w.ob = nil
w.m.Unlock()
}
// GetFunctionality returns a functionality bitmask for the websocket
// connection
func (w *Websocket) GetFunctionality() uint32 {
@@ -723,9 +515,10 @@ func (w *Websocket) SetChannelUnsubscriber(unsubscriber func(channelToUnsubscrib
}
// ManageSubscriptions ensures the subscriptions specified continue to be subscribed to
func (w *Websocket) manageSubscriptions() error {
func (w *Websocket) manageSubscriptions() {
if !w.SupportsFunctionality(WebsocketSubscribeSupported) && !w.SupportsFunctionality(WebsocketUnsubscribeSupported) {
return fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName)
w.DataHandler <- fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName)
return
}
w.Wg.Add(1)
defer func() {
@@ -741,7 +534,7 @@ func (w *Websocket) manageSubscriptions() error {
if w.verbose {
log.Debugf(log.WebsocketMgr, "%v shutdown manageSubscriptions", w.exchangeName)
}
return nil
return
default:
time.Sleep(manageSubscriptionsDelay)
if w.verbose {
@@ -910,3 +703,164 @@ func (w *Websocket) CanUseAuthenticatedEndpoints() bool {
defer w.subscriptionLock.Unlock()
return w.canUseAuthenticatedEndpoints
}
// AddResponseWithID adds data to IDResponses with locks and a nil check
func (w *WebsocketConnection) AddResponseWithID(id int64, data []byte) {
w.Lock()
defer w.Unlock()
if w.IDResponses == nil {
w.IDResponses = make(map[int64][]byte)
}
w.IDResponses[id] = data
}
// Dial sets proxy urls and then connects to the websocket
func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header) error {
if w.ProxyURL != "" {
proxy, err := url.Parse(w.ProxyURL)
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
var conStatus *http.Response
w.Connection, conStatus, err = dialer.Dial(w.URL, headers)
if err != nil {
if conStatus != nil {
return fmt.Errorf("%v %v %v Error: %v", w.URL, conStatus, conStatus.StatusCode, err)
}
return fmt.Errorf("%v Error: %v", w.URL, err)
}
return nil
}
// SendMessage the one true message request. Sends message to WS
func (w *WebsocketConnection) SendMessage(data interface{}) error {
w.Lock()
defer w.Unlock()
json, err := common.JSONEncode(data)
if err != nil {
return err
}
if w.Verbose {
log.Debugf(log.WebsocketMgr,
"%v sending message to websocket %v", w.ExchangeName, string(json))
}
if w.RateLimit > 0 {
time.Sleep(time.Duration(w.RateLimit) * time.Millisecond)
}
return w.Connection.WriteMessage(websocket.TextMessage, json)
}
// SendMessageReturnResponse will send a WS message to the connection
// It will then run a goroutine to await a JSON response
// If there is no response it will return an error
func (w *WebsocketConnection) SendMessageReturnResponse(id int64, request interface{}) ([]byte, error) {
err := w.SendMessage(request)
if err != nil {
return nil, err
}
var wg sync.WaitGroup
wg.Add(1)
go w.WaitForResult(id, &wg)
defer func() {
delete(w.IDResponses, id)
}()
wg.Wait()
if _, ok := w.IDResponses[id]; !ok {
return nil, fmt.Errorf("timeout waiting for response with ID %v", id)
}
return w.IDResponses[id], nil
}
// WaitForResult will keep checking w.IDResponses for a response ID
// If the timer expires, it will return without
func (w *WebsocketConnection) WaitForResult(id int64, wg *sync.WaitGroup) {
defer wg.Done()
timer := time.NewTimer(w.ResponseMaxLimit)
for {
select {
case <-timer.C:
return
default:
w.Lock()
for k := range w.IDResponses {
if k == id {
w.Unlock()
return
}
}
w.Unlock()
time.Sleep(w.ResponseCheckTimeout)
}
}
}
// ReadMessage reads messages, can handle text, gzip and binary
func (w *WebsocketConnection) ReadMessage() (WebsocketResponse, error) {
mType, resp, err := w.Connection.ReadMessage()
if err != nil {
return WebsocketResponse{}, err
}
var standardMessage []byte
switch mType {
case websocket.TextMessage:
standardMessage = resp
case websocket.BinaryMessage:
standardMessage, err = w.parseBinaryResponse(resp)
if err != nil {
return WebsocketResponse{}, err
}
}
if w.Verbose {
log.Debugf(log.WebsocketMgr, "%v Websocket message received: %v",
w.ExchangeName,
string(standardMessage))
}
return WebsocketResponse{Raw: standardMessage, Type: mType}, nil
}
// parseBinaryResponse parses a websocket binary response into a usable byte array
func (w *WebsocketConnection) parseBinaryResponse(resp []byte) ([]byte, error) {
var standardMessage []byte
var err error
// Detect GZIP
if resp[0] == 31 && resp[1] == 139 {
b := bytes.NewReader(resp)
var gReader *gzip.Reader
gReader, err = gzip.NewReader(b)
if err != nil {
return standardMessage, err
}
standardMessage, err = ioutil.ReadAll(gReader)
if err != nil {
return standardMessage, err
}
err = gReader.Close()
if err != nil {
return standardMessage, err
}
} else {
reader := flate.NewReader(bytes.NewReader(resp))
standardMessage, err = ioutil.ReadAll(reader)
if err != nil {
return standardMessage, err
}
err = reader.Close()
if err != nil {
return standardMessage, err
}
}
return standardMessage, nil
}
// GenerateMessageID Creates a messageID to checkout
func (w *WebsocketConnection) GenerateMessageID(useNano bool) int64 {
if useNano {
return time.Now().UnixNano()
}
return time.Now().Unix()
}

View File

@@ -5,9 +5,6 @@ import (
"strings"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
var ws *Websocket
@@ -124,185 +121,6 @@ func TestWebsocket(t *testing.T) {
}
}
func TestInsertingSnapShots(t *testing.T) {
var snapShot1 orderbook.Base
asks := []orderbook.Item{
{Price: 6000, Amount: 1, ID: 1},
{Price: 6001, Amount: 0.5, ID: 2},
{Price: 6002, Amount: 2, ID: 3},
{Price: 6003, Amount: 3, ID: 4},
{Price: 6004, Amount: 5, ID: 5},
{Price: 6005, Amount: 2, ID: 6},
{Price: 6006, Amount: 1.5, ID: 7},
{Price: 6007, Amount: 0.5, ID: 8},
{Price: 6008, Amount: 23, ID: 9},
{Price: 6009, Amount: 9, ID: 10},
{Price: 6010, Amount: 7, ID: 11},
}
bids := []orderbook.Item{
{Price: 5999, Amount: 1, ID: 12},
{Price: 5998, Amount: 0.5, ID: 13},
{Price: 5997, Amount: 2, ID: 14},
{Price: 5996, Amount: 3, ID: 15},
{Price: 5995, Amount: 5, ID: 16},
{Price: 5994, Amount: 2, ID: 17},
{Price: 5993, Amount: 1.5, ID: 18},
{Price: 5992, Amount: 0.5, ID: 19},
{Price: 5991, Amount: 23, ID: 20},
{Price: 5990, Amount: 9, ID: 21},
{Price: 5989, Amount: 7, ID: 22},
}
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = "SPOT"
snapShot1.Pair = currency.NewPairFromString("BTCUSD")
ws.Orderbook.LoadSnapshot(&snapShot1, "ExchangeTest", false)
var snapShot2 orderbook.Base
asks = []orderbook.Item{
{Price: 51, Amount: 1, ID: 1},
{Price: 52, Amount: 0.5, ID: 2},
{Price: 53, Amount: 2, ID: 3},
{Price: 54, Amount: 3, ID: 4},
{Price: 55, Amount: 5, ID: 5},
{Price: 56, Amount: 2, ID: 6},
{Price: 57, Amount: 1.5, ID: 7},
{Price: 58, Amount: 0.5, ID: 8},
{Price: 59, Amount: 23, ID: 9},
{Price: 50, Amount: 9, ID: 10},
{Price: 60, Amount: 7, ID: 11},
}
bids = []orderbook.Item{
{Price: 49, Amount: 1, ID: 12},
{Price: 48, Amount: 0.5, ID: 13},
{Price: 47, Amount: 2, ID: 14},
{Price: 46, Amount: 3, ID: 15},
{Price: 45, Amount: 5, ID: 16},
{Price: 44, Amount: 2, ID: 17},
{Price: 43, Amount: 1.5, ID: 18},
{Price: 42, Amount: 0.5, ID: 19},
{Price: 41, Amount: 23, ID: 20},
{Price: 40, Amount: 9, ID: 21},
{Price: 39, Amount: 7, ID: 22},
}
snapShot2.Asks = asks
snapShot2.Bids = bids
snapShot2.AssetType = "SPOT"
snapShot2.Pair = currency.NewPairFromString("LTCUSD")
ws.Orderbook.LoadSnapshot(&snapShot2, "ExchangeTest", false)
var snapShot3 orderbook.Base
asks = []orderbook.Item{
{Price: 51, Amount: 1, ID: 1},
{Price: 52, Amount: 0.5, ID: 2},
{Price: 53, Amount: 2, ID: 3},
{Price: 54, Amount: 3, ID: 4},
{Price: 55, Amount: 5, ID: 5},
{Price: 56, Amount: 2, ID: 6},
{Price: 57, Amount: 1.5, ID: 7},
{Price: 58, Amount: 0.5, ID: 8},
{Price: 59, Amount: 23, ID: 9},
{Price: 50, Amount: 9, ID: 10},
{Price: 60, Amount: 7, ID: 11},
}
bids = []orderbook.Item{
{Price: 49, Amount: 1, ID: 12},
{Price: 48, Amount: 0.5, ID: 13},
{Price: 47, Amount: 2, ID: 14},
{Price: 46, Amount: 3, ID: 15},
{Price: 45, Amount: 5, ID: 16},
{Price: 44, Amount: 2, ID: 17},
{Price: 43, Amount: 1.5, ID: 18},
{Price: 42, Amount: 0.5, ID: 19},
{Price: 41, Amount: 23, ID: 20},
{Price: 40, Amount: 9, ID: 21},
{Price: 39, Amount: 7, ID: 22},
}
snapShot3.Asks = asks
snapShot3.Bids = bids
snapShot3.AssetType = "FUTURES"
snapShot3.Pair = currency.NewPairFromString("LTCUSD")
ws.Orderbook.LoadSnapshot(&snapShot3, "ExchangeTest", false)
if len(ws.Orderbook.ob) != 3 {
t.Error("test failed - inserting orderbook data")
}
}
func TestUpdate(t *testing.T) {
LTCUSDPAIR := currency.NewPairFromString("LTCUSD")
BTCUSDPAIR := currency.NewPairFromString("BTCUSD")
bidTargets := []orderbook.Item{
{Price: 49, Amount: 24}, // Amend
{Price: 48, Amount: 0}, // Delete
{Price: 1337, Amount: 100}, // Append
{Price: 1336, Amount: 0}, // Ghost delete
}
askTargets := []orderbook.Item{
{Price: 51, Amount: 24}, // Amend
{Price: 52, Amount: 0}, // Delete
{Price: 1337, Amount: 100}, // Append
{Price: 1336, Amount: 0}, // Ghost delete
}
err := ws.Orderbook.Update(bidTargets,
askTargets,
LTCUSDPAIR,
time.Now(),
"ExchangeTest",
"SPOT")
if err != nil {
t.Error("test failed - OrderbookUpdate error", err)
}
err = ws.Orderbook.Update(bidTargets,
askTargets,
LTCUSDPAIR,
time.Now(),
"ExchangeTest",
"FUTURES")
if err != nil {
t.Error("test failed - OrderbookUpdate error", err)
}
bidTargets = []orderbook.Item{
{Price: 5999, Amount: 24}, // Amend
{Price: 5998, Amount: 0}, // Delete
{Price: 1337, Amount: 100}, // Append
{Price: 1336, Amount: 0}, // Ghost delete
}
askTargets = []orderbook.Item{
{Price: 6000, Amount: 24}, // Amend
{Price: 6001, Amount: 0}, // Delete
{Price: 1337, Amount: 100}, // Append
{Price: 1336, Amount: 0}, // Ghost delete
}
err = ws.Orderbook.Update(bidTargets,
askTargets,
BTCUSDPAIR,
time.Now(),
"ExchangeTest",
"SPOT")
if err != nil {
t.Error("test failed - OrderbookUpdate error", err)
}
}
func TestFunctionality(t *testing.T) {
var w Websocket
@@ -409,17 +227,6 @@ func TestUnsubscriptionWithExistingEntry(t *testing.T) {
}
}
// TestManageSubscriptionsWithoutFunctionality logic test
func TestManageSubscriptionsWithoutFunctionality(t *testing.T) {
w := Websocket{
ShutdownC: make(chan struct{}, 1),
}
err := w.manageSubscriptions()
if err == nil {
t.Error("Requires functionality to work")
}
}
// TestManageSubscriptionsStartStop logic test
func TestManageSubscriptionsStartStop(t *testing.T) {
w := Websocket{
@@ -431,17 +238,17 @@ func TestManageSubscriptionsStartStop(t *testing.T) {
close(w.ShutdownC)
}
// TestWsConnectionMonitorNoConnection logic test
func TestWsConnectionMonitorNoConnection(t *testing.T) {
// TestConnectionMonitorNoConnection logic test
func TestConnectionMonitorNoConnection(t *testing.T) {
w := Websocket{}
w.DataHandler = make(chan interface{}, 1)
w.ShutdownC = make(chan struct{}, 1)
w.exchangeName = "hello"
go w.wsConnectionMonitor()
go w.connectionMonitor()
err := <-w.DataHandler
if !strings.EqualFold(err.(error).Error(),
fmt.Sprintf("%v WsConnectionMonitor: websocket disabled, shutting down", w.exchangeName)) {
t.Errorf("expecting error 'WsConnectionMonitor: websocket disabled, shutting down', received '%v'", err)
fmt.Sprintf("%v connectionMonitor: websocket disabled, shutting down", w.exchangeName)) {
t.Errorf("expecting error 'connectionMonitor: websocket disabled, shutting down', received '%v'", err)
}
}

View File

@@ -4,9 +4,10 @@ import (
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
)
// Websocket functionality list and state consts
@@ -95,7 +96,7 @@ type Websocket struct {
// ShutdownC is the main shutdown channel which controls all websocket go funcs
ShutdownC chan struct{}
// Orderbook is a local cache of orderbooks
Orderbook WebsocketOrderbookLocal
Orderbook wsorderbook.WebsocketOrderbookLocal
// Wg defines a wait group for websocket routines for cleanly shutting down
// routines
Wg sync.WaitGroup
@@ -114,14 +115,6 @@ type WebsocketChannelSubscription struct {
Params map[string]interface{}
}
// WebsocketOrderbookLocal defines a local cache of orderbooks for amending,
// appending and deleting changes and updates the main store in orderbook.go
type WebsocketOrderbookLocal struct {
ob []*orderbook.Base
lastUpdated time.Time
m sync.Mutex
}
// WebsocketResponse defines generalised data from the websocket connection
type WebsocketResponse struct {
Type int
@@ -185,3 +178,20 @@ type WebsocketPositionUpdated struct {
AssetType asset.Item
Exchange string
}
// WebsocketConnection contains all the data needed to send a message to a WS
type WebsocketConnection struct {
sync.Mutex
Verbose bool
RateLimit float64
ExchangeName string
URL string
ProxyURL string
Wg sync.WaitGroup
Connection *websocket.Conn
Shutdown chan struct{}
// These are the request IDs and the corresponding response JSON
IDResponses map[int64][]byte
ResponseCheckTimeout time.Duration
ResponseMaxLimit time.Duration
}

View File

@@ -0,0 +1,243 @@
package wsorderbook
import (
"fmt"
"sort"
"sync"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
// Setup sets private variables
func (w *WebsocketOrderbookLocal) Setup(obBufferLimit int, bufferEnabled, sortBuffer, sortBufferByUpdateIDs, updateEntriesByID bool, exchangeName string) {
w.obBufferLimit = obBufferLimit
w.bufferEnabled = bufferEnabled
w.sortBuffer = sortBuffer
w.sortBufferByUpdateIDs = sortBufferByUpdateIDs
w.updateEntriesByID = updateEntriesByID
w.exchangeName = exchangeName
}
// Update updates a local cache using bid targets and ask targets then updates
// main orderbook
// Volume == 0; deletion at price target
// Price target not found; append of price target
// Price target found; amend volume of price target
func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpdate) error {
if (orderbookUpdate.Bids == nil && orderbookUpdate.Asks == nil) ||
(len(orderbookUpdate.Bids) == 0 && len(orderbookUpdate.Asks) == 0) {
return fmt.Errorf("%v cannot have bids and ask targets both nil", w.exchangeName)
}
w.m.Lock()
defer w.m.Unlock()
if _, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]; !ok {
return fmt.Errorf("ob.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
w.exchangeName,
orderbookUpdate.CurrencyPair.String(),
orderbookUpdate.AssetType)
}
if w.bufferEnabled {
overBufferLimit := w.processBufferUpdate(orderbookUpdate)
if !overBufferLimit {
return nil
}
} else {
w.processObUpdate(orderbookUpdate)
}
err := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Process()
if err != nil {
return err
}
if w.bufferEnabled {
// Reset the buffer
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = nil
}
return nil
}
func (w *WebsocketOrderbookLocal) processBufferUpdate(orderbookUpdate *WebsocketOrderbookUpdate) bool {
if w.buffer == nil {
w.buffer = make(map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate)
}
if w.buffer[orderbookUpdate.CurrencyPair] == nil {
w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]WebsocketOrderbookUpdate)
}
if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) <= w.obBufferLimit {
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = append(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], *orderbookUpdate)
if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) < w.obBufferLimit {
return false
}
}
if w.sortBuffer {
// sort by last updated to ensure each update is in order
if w.sortBufferByUpdateIDs {
sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool {
return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateID < w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateID
})
} else {
sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool {
return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateTime.Before(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateTime)
})
}
}
for i := 0; i < len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]); i++ {
w.processObUpdate(&w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i])
}
return true
}
func (w *WebsocketOrderbookLocal) processObUpdate(orderbookUpdate *WebsocketOrderbookUpdate) {
if w.updateEntriesByID {
w.updateByIDAndAction(orderbookUpdate)
} else {
var wg sync.WaitGroup
wg.Add(2)
go w.updateAsksByPrice(orderbookUpdate, &wg)
go w.updateBidsByPrice(orderbookUpdate, &wg)
wg.Wait()
}
}
func (w *WebsocketOrderbookLocal) updateAsksByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) {
for j := 0; j < len(base.Asks); j++ {
found := false
for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Asks); k++ {
if w.ob[base.CurrencyPair][base.AssetType].Asks[k].Price == base.Asks[j].Price {
found = true
if base.Asks[j].Amount == 0 {
w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks[:k],
w.ob[base.CurrencyPair][base.AssetType].Asks[k+1:]...)
break
}
w.ob[base.CurrencyPair][base.AssetType].Asks[k].Amount = base.Asks[j].Amount
break
}
}
if !found {
w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks, base.Asks[j])
}
}
sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Asks, func(i, j int) bool {
return w.ob[base.CurrencyPair][base.AssetType].Asks[i].Price < w.ob[base.CurrencyPair][base.AssetType].Asks[j].Price
})
wg.Done()
}
func (w *WebsocketOrderbookLocal) updateBidsByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) {
for j := 0; j < len(base.Bids); j++ {
found := false
for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Bids); k++ {
if w.ob[base.CurrencyPair][base.AssetType].Bids[k].Price == base.Bids[j].Price {
found = true
if base.Bids[j].Amount == 0 {
w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids[:k],
w.ob[base.CurrencyPair][base.AssetType].Bids[k+1:]...)
break
}
w.ob[base.CurrencyPair][base.AssetType].Bids[k].Amount = base.Bids[j].Amount
break
}
}
if !found {
w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids, base.Bids[j])
}
}
sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Bids, func(i, j int) bool {
return w.ob[base.CurrencyPair][base.AssetType].Bids[i].Price > w.ob[base.CurrencyPair][base.AssetType].Bids[j].Price
})
wg.Done()
}
// updateByIDAndAction will receive an action to execute against the orderbook
// it will then match by IDs instead of price to perform the action
func (w *WebsocketOrderbookLocal) updateByIDAndAction(orderbookUpdate *WebsocketOrderbookUpdate) {
switch orderbookUpdate.Action {
case "update":
for _, target := range orderbookUpdate.Bids {
for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids {
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID {
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].Amount = target.Amount
break
}
}
}
for _, target := range orderbookUpdate.Asks {
for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks {
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID {
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].Amount = target.Amount
break
}
}
}
case "delete":
for _, target := range orderbookUpdate.Bids {
for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids); i++ {
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID {
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[:i],
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i+1:]...)
i--
break
}
}
}
for _, target := range orderbookUpdate.Asks {
for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks); i++ {
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID {
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[:i],
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i+1:]...)
i--
break
}
}
}
case "insert":
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids, orderbookUpdate.Bids...)
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks, orderbookUpdate.Asks...)
}
}
// LoadSnapshot loads initial snapshot of ob data, overwrite allows full
// ob to be completely rewritten because the exchange is a doing a full
// update not an incremental one
func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, overwrite bool) error {
if len(newOrderbook.Asks) == 0 || len(newOrderbook.Bids) == 0 {
return fmt.Errorf("%v snapshot ask and bids are nil", w.exchangeName)
}
w.m.Lock()
defer w.m.Unlock()
if w.ob == nil {
w.ob = make(map[currency.Pair]map[asset.Item]*orderbook.Base)
}
if w.ob[newOrderbook.Pair] == nil {
w.ob[newOrderbook.Pair] = make(map[asset.Item]*orderbook.Base)
}
if w.ob[newOrderbook.Pair][newOrderbook.AssetType] != nil &&
(len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Asks) > 0 ||
len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Bids) > 0) {
if overwrite {
w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook
return newOrderbook.Process()
}
return fmt.Errorf("%v snapshot instance already found", w.exchangeName)
}
w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook
return newOrderbook.Process()
}
// GetOrderbook use sparingly. Modifying anything here will ruin hash calculation and cause problems
func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, assetType asset.Item) *orderbook.Base {
w.m.Lock()
defer w.m.Unlock()
return w.ob[p][assetType]
}
// FlushCache flushes w.ob data to be garbage collected and refreshed when a
// connection is lost and reconnected
func (w *WebsocketOrderbookLocal) FlushCache() {
w.m.Lock()
w.ob = nil
w.buffer = nil
w.m.Unlock()
}

View File

@@ -0,0 +1,582 @@
package wsorderbook
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
var itemArray = [][]orderbook.Item{
{{Price: 1000, Amount: 1, ID: 1}},
{{Price: 2000, Amount: 1, ID: 2}},
{{Price: 3000, Amount: 1, ID: 3}},
{{Price: 3000, Amount: 2, ID: 4}},
{{Price: 4000, Amount: 0, ID: 6}},
{{Price: 5000, Amount: 1, ID: 5}},
}
const (
exchangeName = "exchangeTest"
)
func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, bids []orderbook.Item, err error) {
var snapShot1 orderbook.Base
curr = currency.NewPairFromString("BTCUSD")
asks = []orderbook.Item{
{Price: 4000, Amount: 1, ID: 6},
}
bids = []orderbook.Item{
{Price: 4000, Amount: 1, ID: 6},
}
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
obl = &WebsocketOrderbookLocal{}
err = obl.LoadSnapshot(&snapShot1, false)
return
}
// BenchmarkBufferPerformance demonstrates buffer more performant than multi process calls
func BenchmarkBufferPerformance(b *testing.B) {
obl, curr, asks, bids, err := createSnapshot()
if err != nil {
b.Fatal(err)
}
obl.exchangeName = exchangeName
obl.sortBuffer = true
update := &WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
}
for i := 0; i < b.N; i++ {
randomIndex := rand.Intn(5)
update.Asks = itemArray[randomIndex]
update.Bids = itemArray[randomIndex]
err = obl.Update(update)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkBufferSortingPerformance benchmark
func BenchmarkBufferSortingPerformance(b *testing.B) {
obl, curr, asks, bids, err := createSnapshot()
if err != nil {
b.Fatal(err)
}
obl.exchangeName = exchangeName
obl.sortBuffer = true
obl.bufferEnabled = true
obl.obBufferLimit = 5
update := &WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
}
for i := 0; i < b.N; i++ {
randomIndex := rand.Intn(5)
update.Asks = itemArray[randomIndex]
update.Bids = itemArray[randomIndex]
err = obl.Update(update)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkNoBufferPerformance demonstrates orderbook process less performant than buffer
func BenchmarkNoBufferPerformance(b *testing.B) {
obl, curr, asks, bids, err := createSnapshot()
if err != nil {
b.Fatal(err)
}
obl.exchangeName = exchangeName
update := &WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
}
for i := 0; i < b.N; i++ {
randomIndex := rand.Intn(5)
update.Asks = itemArray[randomIndex]
update.Bids = itemArray[randomIndex]
err = obl.Update(update)
if err != nil {
b.Fatal(err)
}
}
}
// TestHittingTheBuffer logic test
func TestHittingTheBuffer(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
obl.exchangeName = exchangeName
obl.bufferEnabled = true
obl.obBufferLimit = 5
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
t.Log(obl.ob[curr][asset.Spot])
t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
}
}
// TestInsertWithIDs logic test
func TestInsertWithIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
obl.exchangeName = exchangeName
obl.bufferEnabled = true
obl.updateEntriesByID = true
obl.obBufferLimit = 5
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Action: "insert",
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 6 {
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 6 {
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
}
}
// TestSortIDs logic test
func TestSortIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
obl.exchangeName = exchangeName
obl.bufferEnabled = true
obl.sortBufferByUpdateIDs = true
obl.sortBuffer = true
obl.obBufferLimit = 5
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateID: int64(i),
AssetType: asset.Spot,
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
}
}
// TestDeleteWithIDs logic test
func TestDeleteWithIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
obl.exchangeName = exchangeName
obl.updateEntriesByID = true
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Action: "delete",
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 0 {
t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 0 {
t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
}
}
// TestUpdateWithIDs logic test
func TestUpdateWithIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
obl.exchangeName = exchangeName
obl.updateEntriesByID = true
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
bids := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
Action: "update",
})
if err != nil {
t.Fatal(err)
}
}
if len(obl.ob[curr][asset.Spot].Asks) != 1 {
t.Log(obl.ob[curr][asset.Spot])
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
}
if len(obl.ob[curr][asset.Spot].Bids) != 1 {
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
}
}
// TestOutOfOrderIDs logic test
func TestOutOfOrderIDs(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
outOFOrderIDs := []int64{2, 1, 5, 3, 4, 6}
if itemArray[0][0].Price != 1000 {
t.Errorf("expected sorted price to be 3000, received: %v", itemArray[1][0].Price)
}
obl.exchangeName = exchangeName
obl.bufferEnabled = true
obl.sortBuffer = true
obl.obBufferLimit = 5
for i := 0; i < len(itemArray); i++ {
asks := itemArray[i]
err = obl.Update(&WebsocketOrderbookUpdate{
Asks: asks,
CurrencyPair: curr,
UpdateID: outOFOrderIDs[i],
AssetType: asset.Spot,
})
if err != nil {
t.Fatal(err)
}
}
// Index 1 since index 0 is price 7000
if obl.ob[curr][asset.Spot].Asks[1].Price != 2000 {
t.Errorf("expected sorted price to be 3000, received: %v", obl.ob[curr][asset.Spot].Asks[1].Price)
}
}
// TestRunUpdateWithoutSnapshot logic test
func TestRunUpdateWithoutSnapshot(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
curr := currency.NewPairFromString("BTCUSD")
asks := []orderbook.Item{
{Price: 4000, Amount: 1, ID: 8},
}
bids := []orderbook.Item{
{Price: 5999, Amount: 1, ID: 8},
{Price: 4000, Amount: 1, ID: 9},
}
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
obl.exchangeName = exchangeName
err := obl.Update(&WebsocketOrderbookUpdate{
Bids: bids,
Asks: asks,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
})
if err == nil {
t.Fatal("expected an error running update with no snapshot loaded")
}
if err.Error() != "ob.Base could not be found for Exchange exchangeTest CurrencyPair: BTCUSD AssetType: spot" {
t.Fatal(err)
}
}
// TestRunUpdateWithoutAnyUpdates logic test
func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
curr := currency.NewPairFromString("BTCUSD")
snapShot1.Asks = []orderbook.Item{}
snapShot1.Bids = []orderbook.Item{}
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
obl.exchangeName = exchangeName
err := obl.Update(&WebsocketOrderbookUpdate{
Bids: snapShot1.Asks,
Asks: snapShot1.Bids,
CurrencyPair: curr,
UpdateTime: time.Now(),
AssetType: asset.Spot,
})
if err == nil {
t.Fatal("expected an error running update with no snapshot loaded")
}
if err.Error() != fmt.Sprintf("%v cannot have bids and ask targets both nil", exchangeName) {
t.Fatal("expected nil asks and bids error")
}
}
// TestRunSnapshotWithNoData logic test
func TestRunSnapshotWithNoData(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
curr := currency.NewPairFromString("BTCUSD")
snapShot1.Asks = []orderbook.Item{}
snapShot1.Bids = []orderbook.Item{}
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
snapShot1.ExchangeName = "test"
obl.exchangeName = "test"
err := obl.LoadSnapshot(&snapShot1,
false)
if err == nil {
t.Fatal("expected an error loading a snapshot")
}
if err.Error() != "test snapshot ask and bids are nil" {
t.Fatal(err)
}
}
// TestLoadSnapshotWithOverride logic test
func TestLoadSnapshotWithOverride(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
curr := currency.NewPairFromString("BTCUSD")
asks := []orderbook.Item{
{Price: 4000, Amount: 1, ID: 8},
}
bids := []orderbook.Item{
{Price: 4000, Amount: 1, ID: 9},
}
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = curr
err := obl.LoadSnapshot(&snapShot1, false)
if err != nil {
t.Error(err)
}
err = obl.LoadSnapshot(&snapShot1, false)
if err == nil {
t.Error("expected error: 'snapshot instance already found'")
}
err = obl.LoadSnapshot(&snapShot1, true)
if err != nil {
t.Error(err)
}
}
// TestInsertWithIDs logic test
func TestFlushCache(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
if obl.ob[curr][asset.Spot] == nil {
t.Error("expected ob to have ask entries")
}
obl.FlushCache()
if obl.ob[curr][asset.Spot] != nil {
t.Error("expected ob be flushed")
}
}
// TestInsertingSnapShots logic test
func TestInsertingSnapShots(t *testing.T) {
var obl WebsocketOrderbookLocal
var snapShot1 orderbook.Base
asks := []orderbook.Item{
{Price: 6000, Amount: 1, ID: 1},
{Price: 6001, Amount: 0.5, ID: 2},
{Price: 6002, Amount: 2, ID: 3},
{Price: 6003, Amount: 3, ID: 4},
{Price: 6004, Amount: 5, ID: 5},
{Price: 6005, Amount: 2, ID: 6},
{Price: 6006, Amount: 1.5, ID: 7},
{Price: 6007, Amount: 0.5, ID: 8},
{Price: 6008, Amount: 23, ID: 9},
{Price: 6009, Amount: 9, ID: 10},
{Price: 6010, Amount: 7, ID: 11},
}
bids := []orderbook.Item{
{Price: 5999, Amount: 1, ID: 12},
{Price: 5998, Amount: 0.5, ID: 13},
{Price: 5997, Amount: 2, ID: 14},
{Price: 5996, Amount: 3, ID: 15},
{Price: 5995, Amount: 5, ID: 16},
{Price: 5994, Amount: 2, ID: 17},
{Price: 5993, Amount: 1.5, ID: 18},
{Price: 5992, Amount: 0.5, ID: 19},
{Price: 5991, Amount: 23, ID: 20},
{Price: 5990, Amount: 9, ID: 21},
{Price: 5989, Amount: 7, ID: 22},
}
snapShot1.Asks = asks
snapShot1.Bids = bids
snapShot1.AssetType = asset.Spot
snapShot1.Pair = currency.NewPairFromString("BTCUSD")
err := obl.LoadSnapshot(&snapShot1, false)
if err != nil {
t.Fatal(err)
}
var snapShot2 orderbook.Base
asks = []orderbook.Item{
{Price: 51, Amount: 1, ID: 1},
{Price: 52, Amount: 0.5, ID: 2},
{Price: 53, Amount: 2, ID: 3},
{Price: 54, Amount: 3, ID: 4},
{Price: 55, Amount: 5, ID: 5},
{Price: 56, Amount: 2, ID: 6},
{Price: 57, Amount: 1.5, ID: 7},
{Price: 58, Amount: 0.5, ID: 8},
{Price: 59, Amount: 23, ID: 9},
{Price: 50, Amount: 9, ID: 10},
{Price: 60, Amount: 7, ID: 11},
}
bids = []orderbook.Item{
{Price: 49, Amount: 1, ID: 12},
{Price: 48, Amount: 0.5, ID: 13},
{Price: 47, Amount: 2, ID: 14},
{Price: 46, Amount: 3, ID: 15},
{Price: 45, Amount: 5, ID: 16},
{Price: 44, Amount: 2, ID: 17},
{Price: 43, Amount: 1.5, ID: 18},
{Price: 42, Amount: 0.5, ID: 19},
{Price: 41, Amount: 23, ID: 20},
{Price: 40, Amount: 9, ID: 21},
{Price: 39, Amount: 7, ID: 22},
}
snapShot2.Asks = asks
snapShot2.Bids = bids
snapShot2.AssetType = asset.Spot
snapShot2.Pair = currency.NewPairFromString("LTCUSD")
err = obl.LoadSnapshot(&snapShot2, false)
if err != nil {
t.Fatal(err)
}
var snapShot3 orderbook.Base
asks = []orderbook.Item{
{Price: 511, Amount: 1, ID: 1},
{Price: 52, Amount: 0.5, ID: 2},
{Price: 53, Amount: 2, ID: 3},
{Price: 54, Amount: 3, ID: 4},
{Price: 55, Amount: 5, ID: 5},
{Price: 56, Amount: 2, ID: 6},
{Price: 57, Amount: 1.5, ID: 7},
{Price: 58, Amount: 0.5, ID: 8},
{Price: 59, Amount: 23, ID: 9},
{Price: 50, Amount: 9, ID: 10},
{Price: 60, Amount: 7, ID: 11},
}
bids = []orderbook.Item{
{Price: 49, Amount: 1, ID: 12},
{Price: 48, Amount: 0.5, ID: 13},
{Price: 47, Amount: 2, ID: 14},
{Price: 46, Amount: 3, ID: 15},
{Price: 45, Amount: 5, ID: 16},
{Price: 44, Amount: 2, ID: 17},
{Price: 43, Amount: 1.5, ID: 18},
{Price: 42, Amount: 0.5, ID: 19},
{Price: 41, Amount: 23, ID: 20},
{Price: 40, Amount: 9, ID: 21},
{Price: 39, Amount: 7, ID: 22},
}
snapShot3.Asks = asks
snapShot3.Bids = bids
snapShot3.AssetType = "FUTURES"
snapShot3.Pair = currency.NewPairFromString("LTCUSD")
err = obl.LoadSnapshot(&snapShot3, false)
if err != nil {
t.Fatal(err)
}
if obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0] != snapShot1.Asks[0] {
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot1.Asks[0], obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0])
}
if obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0] != snapShot2.Asks[0] {
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot2.Asks[0], obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0])
}
if obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0] != snapShot3.Asks[0] {
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot3.Asks[0], obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0])
}
}
func TestGetOrderbook(t *testing.T) {
obl, curr, _, _, err := createSnapshot()
if err != nil {
t.Fatal(err)
}
ob := obl.GetOrderbook(curr, asset.Spot)
if obl.ob[curr][asset.Spot] != ob {
t.Error("Failed to get orderbook")
}
}
func TestSetup(t *testing.T) {
w := WebsocketOrderbookLocal{}
w.Setup(1, true, true, true, true, "hi")
if w.obBufferLimit != 1 || !w.bufferEnabled || !w.sortBuffer || !w.sortBufferByUpdateIDs || !w.updateEntriesByID || w.exchangeName != "hi" {
t.Errorf("Setup incorrectly loaded %v", w)
}
}

View File

@@ -0,0 +1,35 @@
package wsorderbook
import (
"sync"
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
// WebsocketOrderbookLocal defines a local cache of orderbooks for amending,
// appending and deleting changes and updates the main store in wsorderbook.go
type WebsocketOrderbookLocal struct {
ob map[currency.Pair]map[asset.Item]*orderbook.Base
buffer map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate
obBufferLimit int
bufferEnabled bool
sortBuffer bool
sortBufferByUpdateIDs bool // When timestamps aren't provided, an id can help sort
updateEntriesByID bool // Use the update IDs to match ob entries
exchangeName string
m sync.Mutex
}
// WebsocketOrderbookUpdate stores orderbook updates and dictates what features to use when processing
type WebsocketOrderbookUpdate struct {
UpdateID int64 // Used when no time is provided
UpdateTime time.Time
AssetType asset.Item
Action string // Used in conjunction with UpdateEntriesByID
Bids []orderbook.Item
Asks []orderbook.Item
CurrencyPair currency.Pair
}

Some files were not shown because too many files have changed in this diff Show More