mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Websocket orderbook buffering (#333)
* Initial commit setting up a map orderbook system with a buffer. It will write to the buffer, sort apply to main orderbook and then process. * Moves namespaces again * Updates orderbook to use a sweet new WebsocketOrderbookUpdate type to handle all updates whether its using ID or not. So good. Adds many tests * Starting to implement orderbook update handling per exchange. Updates namespaces again. Hopefuylly will find a way to update via ID not timestamp, too many endpoints dont provide update timestamps * Changes orderbookbuffer to use BufferUpdate type instead of orderbook.Base to achieve more functionality and no need for type conversion functions. Updates tests * Updates all instances of ws.orderbook.Update. Simplifies some orderbook logic * Introduces toggleable buffer. Renames orderbooks. Completes implementation for everywhere but OKGroup due to hash calculation * Implements orderbook update for okgroup, but forgets about the orderbook hash checking * Fixes okgroup checksum calculation. Fixes linting issue. Removes redundant Kraken tests. * Introduces sorting toggle and separates from buffer toggle. Uses benchmarks to highlight performance gains * Fixes Gemini rate limit and parsing. Removes comments and fixes typos * Fixes bitfinex orderbook processing * Inbuilt sorting, minor fixes for websocket implementations. Improves test coverage * Adds surprise LakeBTC websocket support * Fixes data race * Fixes rebasing issues due to namespace movements * Addresses PR nits: moves folder namespace from ws to websocket. Removes line spaces in imports. Fixes lakebtc websocket returns and defer fucntions. Fixes comments * Adds poloniex orderook sorting support * Enables bitstamp and hitbtc orderbook sorting. Fixes poloniex's sorting * Renames namespaces and combines monitor and connection into wshandler. Removes unused SPOT const. Changes how orderbook stuff is loaded. It is done in startup with a setup. Removes exchange name from loadsnapshot as well * Removes the connection.go from rebasing issues. Removes error response from functions used in goroutines * Fixes test with exchange name output change * Fixes issues where copy and paste and replace all were used poorly
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// GetAccountInfo retrieves balances for all enabled currencies on the
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -101,6 +101,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
|
||||
@@ -152,7 +153,6 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
b.WebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: b.Name,
|
||||
URL: b.Websocket.GetWebsocketURL(),
|
||||
@@ -161,6 +161,13 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -15,112 +14,14 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"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 := exchange.FormatExchangeCurrency(b.Name, p)
|
||||
|
||||
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 = ticker.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(),
|
||||
"SPOT")
|
||||
}
|
||||
|
||||
// WSConnect intiates a websocket connection
|
||||
func (b *Binance) WSConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
@@ -175,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:
|
||||
@@ -234,7 +133,7 @@ func (b *Binance) WsHandleData() {
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: b.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Side: trade.EventType,
|
||||
}
|
||||
continue
|
||||
@@ -309,7 +208,7 @@ func (b *Binance) WsHandleData() {
|
||||
currencyPair := currency.NewPairFromString(depth.Pair)
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
continue
|
||||
@@ -317,3 +216,71 @@ func (b *Binance) WsHandleData() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SeedLocalCache seeds depth data
|
||||
func (b *Binance) SeedLocalCache(p currency.Pair) error {
|
||||
var newOrderBook orderbook.Base
|
||||
formattedPair := exchange.FormatExchangeCurrency(b.Name, p)
|
||||
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 = ticker.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: orderbook.Spot,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
@@ -120,6 +120,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
|
||||
@@ -182,6 +183,13 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) {
|
||||
}
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -182,7 +183,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{})
|
||||
@@ -229,45 +234,40 @@ 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),
|
||||
"SPOT",
|
||||
err := b.WsInsertSnapshot(curr,
|
||||
orderbook.Spot,
|
||||
newOrderbook)
|
||||
|
||||
if err != nil {
|
||||
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,
|
||||
orderbook.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),
|
||||
"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),
|
||||
@@ -276,7 +276,7 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
LowPrice: chanData[10].(float64),
|
||||
Pair: currency.NewPairFromString(chanInfo.Pair),
|
||||
Exchange: b.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
}
|
||||
|
||||
case "account":
|
||||
@@ -284,8 +284,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),
|
||||
@@ -317,8 +317,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),
|
||||
@@ -342,8 +342,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)),
|
||||
@@ -406,8 +406,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
|
||||
}
|
||||
@@ -445,7 +445,7 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
Price: trades[0].Price,
|
||||
Amount: newAmount,
|
||||
Exchange: b.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Side: side,
|
||||
}
|
||||
}
|
||||
@@ -462,31 +462,26 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType string, books []W
|
||||
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()}
|
||||
@@ -495,80 +490,36 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType string, books []W
|
||||
|
||||
// WsUpdateOrderbook updates the orderbook list, removing and adding to the
|
||||
// orderbook sides
|
||||
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, book WebsocketBook) error {
|
||||
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType string, 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
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,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"
|
||||
)
|
||||
|
||||
@@ -143,6 +143,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
|
||||
@@ -201,6 +202,13 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -220,23 +221,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,99 +326,80 @@ func (b *Bitmex) wsHandleIncomingData() {
|
||||
}
|
||||
}
|
||||
|
||||
var snapshotloaded = make(map[currency.Pair]map[string]bool)
|
||||
|
||||
// ProcessOrderbook processes orderbook updates
|
||||
func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType string) 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[string]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
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,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"
|
||||
)
|
||||
|
||||
@@ -94,6 +94,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
|
||||
@@ -158,6 +159,13 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -156,22 +156,21 @@ func (b *Bitstamp) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubs
|
||||
return b.WebsocketConn.SendMessage(req)
|
||||
}
|
||||
|
||||
func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType string) error {
|
||||
if len(ob.Asks) == 0 && len(ob.Bids) == 0 {
|
||||
func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, assetType string) 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
|
||||
@@ -181,15 +180,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
|
||||
@@ -198,8 +197,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: orderbook.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -224,17 +228,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)
|
||||
}
|
||||
|
||||
@@ -243,7 +247,7 @@ func (b *Bitstamp) seedOrderBook() error {
|
||||
newOrderBook.Pair = p[x]
|
||||
newOrderBook.AssetType = ticker.Spot
|
||||
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
@@ -71,6 +71,7 @@ func (b *BTSE) SetDefaults() {
|
||||
wshandler.WebsocketUnsubscribeSupported
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup takes in the supplied exchange configuration details and sets params
|
||||
@@ -129,6 +130,13 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,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"
|
||||
)
|
||||
|
||||
@@ -94,7 +94,7 @@ func (b *BTSE) WsHandleData() {
|
||||
b.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Timestamp: time.Now(),
|
||||
Pair: currency.NewPairDelimiter(t.ProductID, "-"),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Exchange: b.GetName(),
|
||||
OpenPrice: price,
|
||||
}
|
||||
@@ -119,14 +119,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
|
||||
@@ -136,14 +136,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
|
||||
@@ -154,19 +154,19 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
|
||||
}
|
||||
|
||||
p := currency.NewPairDelimiter(snapshot.ProductID, "-")
|
||||
base.AssetType = "SPOT"
|
||||
base.AssetType = orderbook.Spot
|
||||
base.Pair = p
|
||||
base.LastUpdated = time.Now()
|
||||
base.ExchangeName = b.Name
|
||||
|
||||
err := base.Process()
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&base, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -95,6 +95,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
|
||||
@@ -158,6 +159,13 @@ func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
c.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -88,7 +89,7 @@ func (c *CoinbasePro) WsHandleData() {
|
||||
c.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Timestamp: ticker.Time,
|
||||
Pair: currency.NewPairFromString(ticker.ProductID),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Exchange: c.GetName(),
|
||||
OpenPrice: ticker.Open24H,
|
||||
HighPrice: ticker.High24H,
|
||||
@@ -177,13 +178,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
|
||||
}
|
||||
@@ -192,13 +193,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
|
||||
}
|
||||
@@ -208,17 +209,17 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
|
||||
}
|
||||
|
||||
pair := currency.NewPairFromString(snapshot.ProductID)
|
||||
base.AssetType = "SPOT"
|
||||
base.AssetType = orderbook.Spot
|
||||
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: pair,
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Exchange: c.GetName(),
|
||||
}
|
||||
|
||||
@@ -227,33 +228,42 @@ 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) == "buy" {
|
||||
Bids = append(Bids, orderbook.Item{Price: price, Amount: volume})
|
||||
if update.Changes[i][0].(string) == "buy" {
|
||||
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(), "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: orderbook.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Exchange: c.GetName(),
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
"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/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"
|
||||
)
|
||||
|
||||
@@ -86,6 +87,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
|
||||
@@ -147,6 +149,13 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
RateLimit: coinutWebsocketRateLimit,
|
||||
}
|
||||
c.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +163,7 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) {
|
||||
func (c *COINUT) GetInstruments() (Instruments, error) {
|
||||
var result Instruments
|
||||
params := make(map[string]interface{})
|
||||
params["sec_type"] = "SPOT"
|
||||
params["sec_type"] = orderbook.Spot
|
||||
|
||||
return result, c.SendHTTPRequest(coinutInstruments, params, false, &result)
|
||||
}
|
||||
|
||||
@@ -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 c COINUT
|
||||
|
||||
@@ -12,7 +12,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
)
|
||||
|
||||
const coinutWebsocketURL = "wss://wsapi.coinut.com"
|
||||
@@ -137,7 +138,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
c.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Timestamp: time.Unix(0, ticker.Timestamp),
|
||||
Exchange: c.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
HighPrice: ticker.HighestBuy,
|
||||
LowPrice: ticker.LowestSell,
|
||||
ClosePrice: ticker.Last,
|
||||
@@ -159,7 +160,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
currencyPair := instrumentListByCode[orderbooksnapshot.InstID]
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: c.GetName(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Pair: currency.NewPairFromString(currencyPair),
|
||||
}
|
||||
case "inst_order_book_update":
|
||||
@@ -177,7 +178,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
currencyPair := instrumentListByCode[orderbookUpdate.InstID]
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: c.GetName(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Pair: currency.NewPairFromString(currencyPair),
|
||||
}
|
||||
case "inst_trade":
|
||||
@@ -199,7 +200,7 @@ func (c *COINUT) wsProcessResponse(resp []byte) {
|
||||
c.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(tradeUpdate.Timestamp, 0),
|
||||
CurrencyPair: currency.NewPairFromString(currencyPair),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Exchange: c.GetName(),
|
||||
Price: tradeUpdate.Price,
|
||||
Side: tradeUpdate.Side,
|
||||
@@ -228,7 +229,7 @@ func (c *COINUT) GetNonce() int64 {
|
||||
func (c *COINUT) WsSetInstrumentList() error {
|
||||
request := wsRequest{
|
||||
Request: "inst_list",
|
||||
SecType: "SPOT",
|
||||
SecType: orderbook.Spot,
|
||||
Nonce: c.WebsocketConn.GenerateMessageID(false),
|
||||
}
|
||||
resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request)
|
||||
@@ -253,18 +254,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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -272,32 +273,25 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID])
|
||||
newOrderBook.AssetType = "SPOT"
|
||||
newOrderBook.AssetType = orderbook.Spot
|
||||
|
||||
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 == "buy" {
|
||||
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
||||
{Price: ob.Price, Amount: ob.Volume}},
|
||||
nil,
|
||||
p,
|
||||
time.Now(),
|
||||
c.GetName(),
|
||||
"SPOT")
|
||||
func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error {
|
||||
p := currency.NewPairFromString(instrumentListByCode[update.InstID])
|
||||
bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{
|
||||
CurrencyPair: p,
|
||||
UpdateID: update.TransID,
|
||||
AssetType: orderbook.Spot,
|
||||
}
|
||||
|
||||
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
||||
{Price: ob.Price, Amount: ob.Volume}},
|
||||
nil,
|
||||
p,
|
||||
time.Now(),
|
||||
c.GetName(),
|
||||
"SPOT")
|
||||
if strings.EqualFold(update.Side, "buy") {
|
||||
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()
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -32,6 +32,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
|
||||
)
|
||||
|
||||
// FeeType custom type for calculating fees based on method
|
||||
@@ -267,6 +269,7 @@ type Base struct {
|
||||
RESTPollingDelay time.Duration
|
||||
WebsocketResponseCheckTimeout time.Duration
|
||||
WebsocketResponseMaxLimit time.Duration
|
||||
WebsocketOrderbookBufferLimit int64
|
||||
AuthenticatedAPISupport bool
|
||||
AuthenticatedWebsocketAPISupport bool
|
||||
APIWithdrawPermissions uint32
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -217,7 +218,7 @@ func TestSetAssetTypes(t *testing.T) {
|
||||
}
|
||||
|
||||
b.Name = defaultTestExchange
|
||||
b.AssetTypes = []string{"SPOT"}
|
||||
b.AssetTypes = []string{orderbook.Spot}
|
||||
err = b.SetAssetTypes()
|
||||
if err != nil {
|
||||
t.Fatalf("Test failed. TestSetAssetTypes. Error %s", err)
|
||||
@@ -255,7 +256,7 @@ func TestSetAssetTypes(t *testing.T) {
|
||||
|
||||
func TestGetAssetTypes(t *testing.T) {
|
||||
testExchange := Base{
|
||||
AssetTypes: []string{"SPOT", "Binary", "Futures"},
|
||||
AssetTypes: []string{orderbook.Spot, "Binary", "Futures"},
|
||||
}
|
||||
|
||||
aT := testExchange.GetAssetTypes()
|
||||
|
||||
@@ -15,7 +15,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,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"
|
||||
)
|
||||
|
||||
@@ -85,6 +85,7 @@ func (g *Gateio) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user configuration
|
||||
@@ -147,6 +148,13 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
RateLimit: gateioWebsocketRateLimit,
|
||||
}
|
||||
g.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -135,7 +136,7 @@ func (g *Gateio) WsHandleData() {
|
||||
g.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Timestamp: time.Now(),
|
||||
Pair: currency.NewPairFromString(c),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Exchange: g.GetName(),
|
||||
ClosePrice: ticker.Close,
|
||||
Quantity: ticker.BaseVolume,
|
||||
@@ -159,15 +160,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: "SPOT",
|
||||
AssetType: orderbook.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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,9 +197,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,
|
||||
@@ -206,9 +207,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,
|
||||
@@ -231,22 +232,23 @@ func (g *Gateio) WsHandleData() {
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = "SPOT"
|
||||
newOrderBook.AssetType = orderbook.Spot
|
||||
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(),
|
||||
"SPOT")
|
||||
err = g.Websocket.Orderbook.Update(
|
||||
&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
CurrencyPair: currency.NewPairFromString(c),
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: orderbook.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
@@ -254,7 +256,7 @@ func (g *Gateio) WsHandleData() {
|
||||
|
||||
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currency.NewPairFromString(c),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Exchange: g.GetName(),
|
||||
}
|
||||
|
||||
@@ -275,7 +277,7 @@ func (g *Gateio) WsHandleData() {
|
||||
g.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Now(),
|
||||
Pair: currency.NewPairFromString(data[7].(string)),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Exchange: g.GetName(),
|
||||
OpenPrice: open,
|
||||
ClosePrice: closePrice,
|
||||
@@ -334,8 +336,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{
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
@@ -116,8 +116,8 @@ func (g *Gemini) SetDefaults() {
|
||||
g.SupportsAutoPairUpdating = true
|
||||
g.SupportsRESTTickerBatching = false
|
||||
g.Requester = request.New(g.Name,
|
||||
request.NewRateLimit(time.Minute, geminiAuthRate),
|
||||
request.NewRateLimit(time.Minute, geminiUnauthRate),
|
||||
request.NewRateLimit(time.Second, geminiAuthRate),
|
||||
request.NewRateLimit(time.Second, geminiUnauthRate),
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
|
||||
g.APIUrlDefault = geminiAPIURL
|
||||
g.APIUrl = g.APIUrlDefault
|
||||
@@ -128,6 +128,7 @@ func (g *Gemini) SetDefaults() {
|
||||
wshandler.WebsocketSequenceNumberSupported
|
||||
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets exchange configuration parameters
|
||||
@@ -186,6 +187,13 @@ func (g *Gemini) Setup(exch *config.ExchangeConfig) {
|
||||
}
|
||||
responseCheckTimeout = exch.WebsocketResponseCheckTimeout
|
||||
responseMaxLimit = exch.WebsocketResponseMaxLimit
|
||||
g.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -14,7 +14,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -110,9 +111,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 {
|
||||
@@ -253,86 +256,75 @@ 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 = "SPOT"
|
||||
newOrderBook.AssetType = orderbook.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: "SPOT",
|
||||
Asset: orderbook.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: "SPOT",
|
||||
AssetType: orderbook.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(),
|
||||
"SPOT")
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
item := orderbook.Item{
|
||||
Amount: result.Events[i].Remaining,
|
||||
Price: result.Events[i].Price,
|
||||
}
|
||||
if result.Events[i].Side == "ask" {
|
||||
asks = append(asks, item)
|
||||
} else {
|
||||
err := g.Websocket.Orderbook.Update([]orderbook.Item{i},
|
||||
nil,
|
||||
pair,
|
||||
time.Now(),
|
||||
g.GetName(),
|
||||
"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: orderbook.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v %v", g.Name, err)
|
||||
}
|
||||
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair,
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Exchange: g.GetName()}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,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"
|
||||
)
|
||||
|
||||
@@ -89,6 +89,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
|
||||
@@ -150,6 +151,13 @@ func (h *HitBTC) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
h.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -114,7 +115,7 @@ func (h *HitBTC) handleSubscriptionUpdates(resp wshandler.WebsocketResponse, ini
|
||||
}
|
||||
h.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Pair: currency.NewPairFromString(ticker.Params.Symbol),
|
||||
Quantity: ticker.Params.Volume,
|
||||
Timestamp: ts,
|
||||
@@ -225,13 +226,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)
|
||||
@@ -239,17 +240,17 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = "SPOT"
|
||||
newOrderBook.AssetType = orderbook.Spot
|
||||
newOrderBook.Pair = p
|
||||
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false)
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: h.GetName(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Pair: p,
|
||||
}
|
||||
|
||||
@@ -257,30 +258,35 @@ 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(), "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: orderbook.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: h.GetName(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Pair: p,
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +22,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"
|
||||
)
|
||||
|
||||
@@ -105,6 +105,7 @@ func (h *HUOBI) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user configuration
|
||||
@@ -177,6 +178,13 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
h.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,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.
|
||||
|
||||
@@ -13,7 +13,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"
|
||||
)
|
||||
|
||||
@@ -242,7 +242,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
h.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, kline.Timestamp),
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Pair: currency.NewPairFromString(data[1]),
|
||||
OpenPrice: kline.Tick.Open,
|
||||
ClosePrice: kline.Tick.Close,
|
||||
@@ -260,7 +260,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
data := common.SplitStrings(trade.Channel, ".")
|
||||
h.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
CurrencyPair: currency.NewPairFromString(data[1]),
|
||||
Timestamp: time.Unix(0, trade.Tick.Timestamp),
|
||||
}
|
||||
@@ -268,37 +268,31 @@ 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(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -100,6 +100,7 @@ func (h *HUOBIHADAX) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
|
||||
}
|
||||
|
||||
@@ -172,6 +173,13 @@ func (h *HUOBIHADAX) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
h.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 supply your own APIKEYS here for due diligence testing
|
||||
|
||||
@@ -13,7 +13,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"
|
||||
)
|
||||
|
||||
@@ -244,7 +244,7 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) {
|
||||
h.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, kline.Timestamp),
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Pair: currency.NewPairFromString(data[1]),
|
||||
OpenPrice: kline.Tick.Open,
|
||||
ClosePrice: kline.Tick.Close,
|
||||
@@ -262,7 +262,7 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) {
|
||||
data := common.SplitStrings(trade.Channel, ".")
|
||||
h.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
CurrencyPair: currency.NewPairFromString(data[1]),
|
||||
Timestamp: time.Unix(0, trade.Tick.Timestamp),
|
||||
}
|
||||
@@ -270,37 +270,31 @@ 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(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@ func (k *Kraken) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
k.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
k.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
|
||||
k.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets current exchange configuration
|
||||
@@ -161,6 +161,13 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
k.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
package kraken
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
var k Kraken
|
||||
@@ -647,107 +644,6 @@ func TestWithdrawCancel(t *testing.T) {
|
||||
|
||||
// ---------------------------- Websocket tests -----------------------------------------
|
||||
|
||||
// TestOrderbookBufferReset websocket test
|
||||
func TestOrderbookBufferReset(t *testing.T) {
|
||||
if k.Name == "" {
|
||||
k.SetDefaults()
|
||||
TestSetup(t)
|
||||
}
|
||||
if !k.Websocket.IsEnabled() {
|
||||
t.Skip("Websocket not enabled, skipping")
|
||||
}
|
||||
var obUpdates []string
|
||||
obpartial := `[0,{"as":[["5541.30000","2.50700000","0"]],"bs":[["5541.20000","1.52900000","0"]]}]`
|
||||
for i := 1; i < orderbookBufferLimit+2; i++ {
|
||||
obUpdates = append(obUpdates, fmt.Sprintf(`[0,{"a":[["5541.30000","2.50700000","%v"]],"b":[["5541.30000","1.00000000","%v"]]}]`, i, i))
|
||||
}
|
||||
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
var dataResponse WebsocketDataResponse
|
||||
err := common.JSONDecode([]byte(obpartial), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData := dataResponse[1].(map[string]interface{})
|
||||
channelData := WebsocketChannelData{
|
||||
ChannelID: 0,
|
||||
Subscription: "orderbook",
|
||||
Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"),
|
||||
}
|
||||
|
||||
k.wsProcessOrderBookPartial(
|
||||
&channelData,
|
||||
obData,
|
||||
)
|
||||
|
||||
for i := 0; i < len(obUpdates); i++ {
|
||||
err = common.JSONDecode([]byte(obUpdates[i]), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData = dataResponse[1].(map[string]interface{})
|
||||
if i < len(obUpdates)-1 {
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
} else if i == len(obUpdates)-1 {
|
||||
k.wsProcessOrderBookUpdate(&channelData)
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
if len(orderbookBuffer[channelData.ChannelID]) != 1 {
|
||||
t.Error("Buffer should have 1 entry after being reset")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrderbookBufferReset websocket test
|
||||
func TestOrderBookOutOfOrder(t *testing.T) {
|
||||
if k.Name == "" {
|
||||
k.SetDefaults()
|
||||
TestSetup(t)
|
||||
}
|
||||
if !k.Websocket.IsEnabled() {
|
||||
t.Skip("Websocket not enabled, skipping")
|
||||
}
|
||||
obpartial := `[0,{"as":[["5541.30000","2.50700000","0"]],"bs":[["5541.20000","1.52900000","5"]]}]`
|
||||
obupdate1 := `[0,{"a":[["5541.30000","0.00000000","1"]],"b":[["5541.30000","0.00000000","3"]]}]`
|
||||
obupdate2 := `[0,{"a":[["5541.30000","2.50700000","2"]],"b":[["5541.30000","0.00000000","1"]]}]`
|
||||
|
||||
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
var dataResponse WebsocketDataResponse
|
||||
err := common.JSONDecode([]byte(obpartial), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData := dataResponse[1].(map[string]interface{})
|
||||
channelData := WebsocketChannelData{
|
||||
ChannelID: 0,
|
||||
Subscription: "orderbook",
|
||||
Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"),
|
||||
}
|
||||
|
||||
k.wsProcessOrderBookPartial(
|
||||
&channelData,
|
||||
obData,
|
||||
)
|
||||
|
||||
err = common.JSONDecode([]byte(obupdate1), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData = dataResponse[1].(map[string]interface{})
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
|
||||
err = common.JSONDecode([]byte(obupdate2), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData = dataResponse[1].(map[string]interface{})
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
|
||||
err = k.wsProcessOrderBookUpdate(&channelData)
|
||||
if !strings.Contains(err.Error(), "orderbook update out of order") {
|
||||
t.Error("Expected out of order orderbook error")
|
||||
}
|
||||
}
|
||||
|
||||
func setupWsTests(t *testing.T) {
|
||||
if wsSetupRan {
|
||||
return
|
||||
|
||||
@@ -5,16 +5,15 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -40,21 +39,13 @@ const (
|
||||
krakenWsSpread = "spread"
|
||||
krakenWsOrderbook = "book"
|
||||
// Only supported asset type
|
||||
krakenWsAssetType = orderbook.Spot
|
||||
orderbookBufferLimit = 3
|
||||
krakenWsRateLimit = 50
|
||||
)
|
||||
|
||||
// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time
|
||||
var orderbookMutex sync.Mutex
|
||||
var subscriptionChannelPair []WebsocketChannelData
|
||||
|
||||
// krakenOrderBooks TODO THIS IS A TEMPORARY SOLUTION UNTIL ENGINE BRANCH IS MERGED
|
||||
// WS orderbook data can only rely on WS orderbook data
|
||||
// Currently REST and WS runs simultaneously, dirtying the data
|
||||
var krakenOrderBooks map[int64]orderbook.Base
|
||||
|
||||
// orderbookBuffer Stores orderbook updates per channel
|
||||
var orderbookBuffer map[int64][]orderbook.Base
|
||||
var subscribeToDefaultChannels = true
|
||||
|
||||
// Channels require a topic and a currency
|
||||
@@ -227,22 +218,6 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp
|
||||
// addNewSubscriptionChannelData stores channel ids, pairs and subscription types to an array
|
||||
// allowing correlation between subscriptions and returned data
|
||||
func addNewSubscriptionChannelData(response *WebsocketEventResponse) {
|
||||
for i := range subscriptionChannelPair {
|
||||
if response.ChannelID != subscriptionChannelPair[i].ChannelID {
|
||||
continue
|
||||
}
|
||||
// kill the stale orderbooks due to resubscribing
|
||||
if orderbookBuffer == nil {
|
||||
orderbookBuffer = make(map[int64][]orderbook.Base)
|
||||
}
|
||||
orderbookBuffer[response.ChannelID] = []orderbook.Base{}
|
||||
if krakenOrderBooks == nil {
|
||||
krakenOrderBooks = make(map[int64]orderbook.Base)
|
||||
}
|
||||
krakenOrderBooks[response.ChannelID] = orderbook.Base{}
|
||||
return
|
||||
}
|
||||
|
||||
// We change the / to - to maintain compatibility with REST/config
|
||||
pair := currency.NewPairWithDelimiter(response.Pair.Base.String(), response.Pair.Quote.String(), "-")
|
||||
subscriptionChannelPair = append(subscriptionChannelPair, WebsocketChannelData{
|
||||
@@ -346,16 +321,13 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
|
||||
if asksExist || bidsExist {
|
||||
k.wsRequestMtx.Lock()
|
||||
defer k.wsRequestMtx.Unlock()
|
||||
k.wsProcessOrderBookBuffer(channelData, obData)
|
||||
if len(orderbookBuffer[channelData.ChannelID]) >= orderbookBufferLimit {
|
||||
err := k.wsProcessOrderBookUpdate(channelData)
|
||||
if err != nil {
|
||||
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
|
||||
Channel: krakenWsOrderbook,
|
||||
Currency: channelData.Pair,
|
||||
}
|
||||
k.Websocket.ResubscribeToChannel(subscriptionToRemove)
|
||||
err := k.wsProcessOrderBookUpdate(channelData, obData)
|
||||
if err != nil {
|
||||
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
|
||||
Channel: krakenWsOrderbook,
|
||||
Currency: channelData.Pair,
|
||||
}
|
||||
k.Websocket.ResubscribeToChannel(subscriptionToRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,7 +335,7 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
|
||||
|
||||
// wsProcessOrderBookPartial creates a new orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, obData map[string]interface{}) {
|
||||
ob := orderbook.Base{
|
||||
base := orderbook.Base{
|
||||
Pair: channelData.Pair,
|
||||
AssetType: orderbook.Spot,
|
||||
}
|
||||
@@ -375,11 +347,10 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
asks := askData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(asks[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
|
||||
ob.Asks = append(ob.Asks, orderbook.Item{
|
||||
base.Asks = append(base.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
|
||||
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -387,17 +358,15 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
highestLastUpdate = askUpdatedTime
|
||||
}
|
||||
}
|
||||
|
||||
bidData := obData["bs"].([]interface{})
|
||||
for i := range bidData {
|
||||
bids := bidData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(bids[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
|
||||
ob.Bids = append(ob.Bids, orderbook.Item{
|
||||
base.Bids = append(base.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
|
||||
timeData, _ := strconv.ParseFloat(bids[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
bidUpdateTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -405,33 +374,25 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
highestLastUpdate = bidUpdateTime
|
||||
}
|
||||
}
|
||||
|
||||
ob.LastUpdated = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&ob, k.Name, true)
|
||||
base.LastUpdated = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&base, true)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: k.Name,
|
||||
Asset: orderbook.Spot,
|
||||
Pair: channelData.Pair,
|
||||
}
|
||||
|
||||
if krakenOrderBooks == nil {
|
||||
krakenOrderBooks = make(map[int64]orderbook.Base)
|
||||
}
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
}
|
||||
|
||||
func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obData map[string]interface{}) {
|
||||
ob := orderbook.Base{
|
||||
AssetType: orderbook.Spot,
|
||||
ExchangeName: k.Name,
|
||||
Pair: channelData.Pair,
|
||||
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, obData map[string]interface{}) error {
|
||||
update := wsorderbook.WebsocketOrderbookUpdate{
|
||||
AssetType: krakenWsAssetType,
|
||||
CurrencyPair: channelData.Pair,
|
||||
}
|
||||
|
||||
var highestLastUpdate time.Time
|
||||
// Ask data is not always sent
|
||||
if _, ok := obData["a"]; ok {
|
||||
@@ -440,11 +401,10 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
|
||||
asks := askData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(asks[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
|
||||
ob.Asks = append(ob.Asks, orderbook.Item{
|
||||
update.Asks = append(update.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
|
||||
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -460,7 +420,7 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
|
||||
bids := bidData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(bids[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
|
||||
ob.Bids = append(ob.Bids, orderbook.Item{
|
||||
update.Bids = append(update.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
@@ -472,193 +432,20 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
|
||||
}
|
||||
}
|
||||
}
|
||||
ob.LastUpdated = highestLastUpdate
|
||||
if orderbookBuffer == nil {
|
||||
orderbookBuffer = make(map[int64][]orderbook.Base)
|
||||
}
|
||||
orderbookBuffer[channelData.ChannelID] = append(orderbookBuffer[channelData.ChannelID], ob)
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Adding orderbook to buffer for channel %v. Lastupdated: %v. %v / %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
ob.LastUpdated,
|
||||
len(orderbookBuffer[channelData.ChannelID]),
|
||||
orderbookBufferLimit)
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData) error {
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Current orderbook 'LastUpdated': %v",
|
||||
k.Name,
|
||||
krakenOrderBooks[channelData.ChannelID].LastUpdated)
|
||||
}
|
||||
lowestLastUpdated := orderbookBuffer[channelData.ChannelID][0].LastUpdated
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Sorting orderbook. Earliest 'LastUpdated' entry: %v",
|
||||
k.Name,
|
||||
lowestLastUpdated)
|
||||
}
|
||||
sort.Slice(orderbookBuffer[channelData.ChannelID], func(i, j int) bool {
|
||||
return orderbookBuffer[channelData.ChannelID][i].LastUpdated.Before(orderbookBuffer[channelData.ChannelID][j].LastUpdated)
|
||||
})
|
||||
|
||||
lowestLastUpdated = orderbookBuffer[channelData.ChannelID][0].LastUpdated
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Sorted orderbook. Earliest 'LastUpdated' entry: %v",
|
||||
k.Name,
|
||||
lowestLastUpdated)
|
||||
}
|
||||
// The earliest update has to be after the previously stored orderbook
|
||||
if krakenOrderBooks[channelData.ChannelID].LastUpdated.After(lowestLastUpdated) {
|
||||
err := fmt.Errorf("%v orderbook update out of order. Existing: %v, Attempted: %v",
|
||||
k.Name,
|
||||
krakenOrderBooks[channelData.ChannelID].LastUpdated,
|
||||
lowestLastUpdated)
|
||||
k.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
|
||||
k.updateChannelOrderbookEntries(channelData)
|
||||
highestLastUpdate := orderbookBuffer[channelData.ChannelID][len(orderbookBuffer[channelData.ChannelID])-1].LastUpdated
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Saving orderbook. Lastupdated: %v",
|
||||
k.Name,
|
||||
highestLastUpdate)
|
||||
}
|
||||
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.LastUpdated = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&ob, k.Name, true)
|
||||
update.UpdateTime = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.Update(&update)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: k.Name,
|
||||
Asset: orderbook.Spot,
|
||||
Pair: channelData.Pair,
|
||||
}
|
||||
// Reset the buffer
|
||||
orderbookBuffer[channelData.ChannelID] = []orderbook.Base{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookEntries(channelData *WebsocketChannelData) {
|
||||
for i := 0; i < len(orderbookBuffer[channelData.ChannelID]); i++ {
|
||||
for j := 0; j < len(orderbookBuffer[channelData.ChannelID][i].Asks); j++ {
|
||||
k.updateChannelOrderbookAsks(i, j, channelData)
|
||||
}
|
||||
for j := 0; j < len(orderbookBuffer[channelData.ChannelID][i].Bids); j++ {
|
||||
k.updateChannelOrderbookBids(i, j, channelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookAsks(i, j int, channelData *WebsocketChannelData) {
|
||||
askFound := k.updateChannelOrderbookAsk(i, j, channelData)
|
||||
if !askFound {
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Adding Ask for channel %v. Price %v. Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Asks = append(ob.Asks, orderbookBuffer[channelData.ChannelID][i].Asks[j])
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookAsk(i, j int, channelData *WebsocketChannelData) bool {
|
||||
askFound := false
|
||||
for l := 0; l < len(krakenOrderBooks[channelData.ChannelID].Asks); l++ {
|
||||
if krakenOrderBooks[channelData.ChannelID].Asks[l].Price == orderbookBuffer[channelData.ChannelID][i].Asks[j].Price {
|
||||
askFound = true
|
||||
if orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount == 0 {
|
||||
// Remove existing entry
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Removing Ask for channel %v. Price %v. Old amount %v. Buffer %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount, i)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Asks = append(ob.Asks[:l], ob.Asks[l+1:]...)
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
l--
|
||||
} else if krakenOrderBooks[channelData.ChannelID].Asks[l].Amount != orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount {
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Updating Ask for channel %v. Price %v. Old amount %v, New Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount)
|
||||
}
|
||||
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount = orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount
|
||||
}
|
||||
return askFound
|
||||
}
|
||||
}
|
||||
return askFound
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookBids(i, j int, channelData *WebsocketChannelData) {
|
||||
bidFound := k.updateChannelOrderbookBid(i, j, channelData)
|
||||
if !bidFound {
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Adding Bid for channel %v. Price %v. Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Bids = append(ob.Bids, orderbookBuffer[channelData.ChannelID][i].Bids[j])
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookBid(i, j int, channelData *WebsocketChannelData) bool {
|
||||
bidFound := false
|
||||
for l := 0; l < len(krakenOrderBooks[channelData.ChannelID].Bids); l++ {
|
||||
if krakenOrderBooks[channelData.ChannelID].Bids[l].Price == orderbookBuffer[channelData.ChannelID][i].Bids[j].Price {
|
||||
bidFound = true
|
||||
if orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount == 0 {
|
||||
// Remove existing entry
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Removing Bid for channel %v. Price %v. Old amount %v. Buffer %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount, i)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Bids = append(ob.Bids[:l], ob.Bids[l+1:]...)
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
l--
|
||||
} else if krakenOrderBooks[channelData.ChannelID].Bids[l].Amount != orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount {
|
||||
if k.Verbose {
|
||||
log.Debugf("%v Updating Bid for channel %v. Price %v. Old amount %v, New Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount)
|
||||
}
|
||||
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount = orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount
|
||||
}
|
||||
return bidFound
|
||||
}
|
||||
}
|
||||
return bidFound
|
||||
}
|
||||
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interface{}) {
|
||||
candleData := data.([]interface{})
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,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"
|
||||
)
|
||||
|
||||
@@ -41,6 +41,7 @@ const (
|
||||
// LakeBTC is the overarching type across the LakeBTC package
|
||||
type LakeBTC struct {
|
||||
exchange.Base
|
||||
WebsocketConn
|
||||
}
|
||||
|
||||
// SetDefaults sets LakeBTC defaults
|
||||
@@ -67,6 +68,10 @@ func (l *LakeBTC) SetDefaults() {
|
||||
l.APIUrlDefault = lakeBTCAPIURL
|
||||
l.APIUrl = l.APIUrlDefault
|
||||
l.Websocket = wshandler.New()
|
||||
l.Websocket.Functionality = wshandler.WebsocketOrderbookSupported |
|
||||
wshandler.WebsocketTradeDataSupported |
|
||||
wshandler.WebsocketSubscribeSupported
|
||||
l.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets exchange configuration profile
|
||||
@@ -105,6 +110,25 @@ func (l *LakeBTC) Setup(exch *config.ExchangeConfig) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = l.Websocket.Setup(l.WsConnect,
|
||||
l.Subscribe,
|
||||
nil,
|
||||
exch.Name,
|
||||
exch.Websocket,
|
||||
exch.Verbose,
|
||||
lakeBTCWSURL,
|
||||
exch.WebsocketURL,
|
||||
exch.AuthenticatedWebsocketAPISupport)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
l.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package lakebtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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/websocket/wshandler"
|
||||
)
|
||||
|
||||
var l LakeBTC
|
||||
var setupRan bool
|
||||
|
||||
// Please add your own APIkeys to do correct due diligence testing.
|
||||
const (
|
||||
@@ -19,21 +23,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.AuthenticatedAPISupport = true
|
||||
lakebtcConfig.APIKey = apiKey
|
||||
lakebtcConfig.APISecret = apiSecret
|
||||
lakebtcConfig.Websocket = true
|
||||
l.Setup(&lakebtcConfig)
|
||||
l.WebsocketURL = lakeBTCWSURL
|
||||
setupRan = true
|
||||
}
|
||||
lakebtcConfig.AuthenticatedAPISupport = true
|
||||
lakebtcConfig.APIKey = apiKey
|
||||
lakebtcConfig.APISecret = apiSecret
|
||||
|
||||
l.Setup(&lakebtcConfig)
|
||||
}
|
||||
|
||||
func TestGetTradablePairs(t *testing.T) {
|
||||
@@ -445,3 +455,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
248
exchanges/lakebtc/lakebtc_websocket.go
Normal file
248
exchanges/lakebtc/lakebtc_websocket.go
Normal file
@@ -0,0 +1,248 @@
|
||||
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/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.GetEnabledCurrencies()
|
||||
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("%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("%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("%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: orderbook.Spot,
|
||||
Exchange: l.GetName(),
|
||||
EventType: orderbook.Spot,
|
||||
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: orderbook.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: orderbook.Spot,
|
||||
Exchange: l.GetName(),
|
||||
Quantity: vol,
|
||||
HighPrice: high,
|
||||
LowPrice: low,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -268,8 +268,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
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -62,4 +62,5 @@ func (o *OKCoin) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"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.
|
||||
@@ -849,7 +850,7 @@ func TestSendWsMessages(t *testing.T) {
|
||||
func TestGetAssetTypeFromTableName(t *testing.T) {
|
||||
str := "spot/candle300s:BTC-USDT"
|
||||
spot := o.GetAssetTypeFromTableName(str)
|
||||
if spot != "SPOT" {
|
||||
if spot != orderbook.Spot {
|
||||
t.Errorf("Error, expected 'SPOT', received: '%v'", spot)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -87,6 +87,7 @@ func (o *OKEX) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -160,6 +160,13 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
o.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -16,7 +15,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -465,7 +465,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
|
||||
}
|
||||
@@ -480,43 +480,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.GetOrderbookEx(instrument, o.GetAssetTypeFromTableName(tableName))
|
||||
update := wsorderbook.WebsocketOrderbookUpdate{
|
||||
AssetType: orderbook.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(err)
|
||||
}
|
||||
if internalOrderbook.LastUpdated.After(wsEventData.Timestamp) {
|
||||
if o.Verbose {
|
||||
log.Errorf("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, orderbook.Spot)
|
||||
checksum := o.CalculateUpdateOrderbookChecksum(updatedOb)
|
||||
if checksum == wsEventData.Checksum {
|
||||
if o.Verbose {
|
||||
log.Debug("Orderbook valid")
|
||||
}
|
||||
internalOrderbook.LastUpdated = wsEventData.Timestamp
|
||||
if o.Verbose {
|
||||
log.Debug("Internalising orderbook")
|
||||
}
|
||||
|
||||
err := o.Websocket.Orderbook.LoadSnapshot(&internalOrderbook, o.GetName(), true)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: o.GetAssetTypeFromTableName(tableName),
|
||||
Pair: instrument,
|
||||
}
|
||||
|
||||
} else {
|
||||
if o.Verbose {
|
||||
log.Debug("Orderbook invalid")
|
||||
@@ -526,35 +512,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)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -95,6 +95,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
|
||||
@@ -155,6 +156,13 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
p.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,8 @@ 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"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -150,12 +151,12 @@ func (p *Poloniex) WsHandleData() {
|
||||
|
||||
p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: p.GetName(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Pair: currency.NewPairFromString(currencyPair),
|
||||
}
|
||||
case "o":
|
||||
currencyPair := CurrencyPairID[chanID]
|
||||
err := p.WsProcessOrderbookUpdate(dataL3, currencyPair)
|
||||
err := p.WsProcessOrderbookUpdate(int64(data[1].(float64)), dataL3, currencyPair)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
continue
|
||||
@@ -163,7 +164,7 @@ func (p *Poloniex) WsHandleData() {
|
||||
|
||||
p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: p.GetName(),
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Pair: currency.NewPairFromString(currencyPair),
|
||||
}
|
||||
case "t":
|
||||
@@ -217,7 +218,7 @@ func (p *Poloniex) wsHandleTickerData(data []interface{}) {
|
||||
p.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Timestamp: time.Now(),
|
||||
Exchange: p.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
LowPrice: t.LowestAsk,
|
||||
HighPrice: t.HighestBid,
|
||||
}
|
||||
@@ -321,43 +322,35 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = "SPOT"
|
||||
newOrderBook.AssetType = orderbook.Spot
|
||||
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(),
|
||||
"SPOT")
|
||||
update := &wsorderbook.WebsocketOrderbookUpdate{
|
||||
CurrencyPair: cP,
|
||||
AssetType: orderbook.Spot,
|
||||
UpdateID: sequenceNumber,
|
||||
}
|
||||
|
||||
return p.Websocket.Orderbook.Update([]orderbook.Item{{Price: price, Amount: volume}},
|
||||
nil,
|
||||
cP,
|
||||
time.Now(),
|
||||
p.GetName(),
|
||||
"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)
|
||||
}
|
||||
|
||||
// CurrencyPairID contains a list of IDS for currency pairs.
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package wshandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -95,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()
|
||||
@@ -116,13 +122,13 @@ 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(err)
|
||||
}
|
||||
if w.verbose {
|
||||
log.Debugf("%v WsConnectionMonitor exiting", w.exchangeName)
|
||||
log.Debugf("%v connectionMonitor exiting", w.exchangeName)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -198,7 +204,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("%v shutting down websocket channels", w.exchangeName)
|
||||
}
|
||||
@@ -225,11 +230,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("%v shutdown error: %v", w.exchangeName, err)
|
||||
w.DataHandler <- fmt.Errorf("%v shutdown error: %v", w.exchangeName, err)
|
||||
}
|
||||
log.Infof("%v reconnecting to websocket", w.exchangeName)
|
||||
w.m.Lock()
|
||||
@@ -237,9 +242,8 @@ func (w *Websocket) WebsocketReset() error {
|
||||
w.m.Unlock()
|
||||
err = w.Connect()
|
||||
if err != nil {
|
||||
log.Errorf("%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
|
||||
@@ -343,7 +347,6 @@ func (w *Websocket) SetWsStatusAndConnection(enabled bool) error {
|
||||
enabled)
|
||||
}
|
||||
w.enabled = enabled
|
||||
|
||||
if !w.init {
|
||||
if enabled {
|
||||
if w.connected {
|
||||
@@ -419,216 +422,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, assetType string) 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, assetType, 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 {
|
||||
@@ -721,9 +514,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() {
|
||||
@@ -739,7 +533,7 @@ func (w *Websocket) manageSubscriptions() error {
|
||||
if w.verbose {
|
||||
log.Debugf("%v shutdown manageSubscriptions", w.exchangeName)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
default:
|
||||
time.Sleep(manageSubscriptionsDelay)
|
||||
if w.verbose {
|
||||
@@ -908,3 +702,163 @@ 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("%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("%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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
)
|
||||
|
||||
// Websocket functionality list and state consts
|
||||
@@ -94,7 +95,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
|
||||
@@ -113,14 +114,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
|
||||
@@ -184,3 +177,20 @@ type WebsocketPositionUpdated struct {
|
||||
AssetType string
|
||||
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
|
||||
}
|
||||
242
exchanges/websocket/wsorderbook/wsorderbook.go
Normal file
242
exchanges/websocket/wsorderbook/wsorderbook.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package wsorderbook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"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[string][]WebsocketOrderbookUpdate)
|
||||
}
|
||||
if w.buffer[orderbookUpdate.CurrencyPair] == nil {
|
||||
w.buffer[orderbookUpdate.CurrencyPair] = make(map[string][]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[string]*orderbook.Base)
|
||||
}
|
||||
if w.ob[newOrderbook.Pair] == nil {
|
||||
w.ob[newOrderbook.Pair] = make(map[string]*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 string) *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()
|
||||
}
|
||||
582
exchanges/websocket/wsorderbook/wsorderbook_test.go
Normal file
582
exchanges/websocket/wsorderbook/wsorderbook_test.go
Normal 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/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"
|
||||
spot = orderbook.Spot
|
||||
)
|
||||
|
||||
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 = 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: 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: 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: 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: spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][spot].Asks) != 3 {
|
||||
t.Log(obl.ob[curr][spot])
|
||||
t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][spot].Bids) != 3 {
|
||||
t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][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: spot,
|
||||
Action: "insert",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][spot].Asks) != 6 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][spot].Bids) != 6 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][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: spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][spot].Asks) != 3 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][spot].Bids) != 3 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][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: spot,
|
||||
Action: "delete",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][spot].Asks) != 0 {
|
||||
t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][spot].Bids) != 0 {
|
||||
t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][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: spot,
|
||||
Action: "update",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][spot].Asks) != 1 {
|
||||
t.Log(obl.ob[curr][spot])
|
||||
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][spot].Bids) != 1 {
|
||||
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][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: spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Index 1 since index 0 is price 7000
|
||||
if obl.ob[curr][spot].Asks[1].Price != 2000 {
|
||||
t.Errorf("expected sorted price to be 3000, received: %v", obl.ob[curr][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 = spot
|
||||
snapShot1.Pair = curr
|
||||
obl.exchangeName = exchangeName
|
||||
err := obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: 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 = spot
|
||||
snapShot1.Pair = curr
|
||||
obl.exchangeName = exchangeName
|
||||
err := obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: snapShot1.Asks,
|
||||
Asks: snapShot1.Bids,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: 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 = 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 = 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][spot] == nil {
|
||||
t.Error("expected ob to have ask entries")
|
||||
}
|
||||
obl.FlushCache()
|
||||
if obl.ob[curr][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 = 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 = 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, spot)
|
||||
if obl.ob[curr][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)
|
||||
}
|
||||
}
|
||||
34
exchanges/websocket/wsorderbook/wsorderbook_types.go
Normal file
34
exchanges/websocket/wsorderbook/wsorderbook_types.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package wsorderbook
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"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[string]*orderbook.Base
|
||||
buffer map[currency.Pair]map[string][]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 string
|
||||
Action string // Used in conjunction with UpdateEntriesByID
|
||||
Bids []orderbook.Item
|
||||
Asks []orderbook.Item
|
||||
CurrencyPair currency.Pair
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package wshandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
// 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("%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("%v Websocket message received: %v",
|
||||
w.ExchangeName,
|
||||
string(standardMessage))
|
||||
}
|
||||
return WebsocketResponse{Raw: standardMessage, Type: mType}, nil
|
||||
}
|
||||
|
||||
// parseBinaryResponse parses a websocket binaray 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()
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package wshandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
const (
|
||||
websocketTestURL = "wss://www.bitmex.com/realtime"
|
||||
returnResponseURL = "wss://ws.kraken.com"
|
||||
useProxyTests = false // Disabled by default. Freely available proxy servers that work all the time are difficult to find
|
||||
proxyURL = "http://212.186.171.4:80" // Replace with a usable proxy server
|
||||
)
|
||||
|
||||
var wc *WebsocketConnection
|
||||
var dialer websocket.Dialer
|
||||
|
||||
type testStruct struct {
|
||||
Error error
|
||||
WC WebsocketConnection
|
||||
}
|
||||
|
||||
type testRequest struct {
|
||||
Event string `json:"event"`
|
||||
RequestID int64 `json:"reqid,omitempty"`
|
||||
Pairs []string `json:"pair"`
|
||||
Subscription testRequestData `json:"subscription,omitempty"`
|
||||
}
|
||||
|
||||
// testRequestData contains details on WS channel
|
||||
type testRequestData struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Interval int64 `json:"interval,omitempty"`
|
||||
Depth int64 `json:"depth,omitempty"`
|
||||
}
|
||||
|
||||
type testResponse struct {
|
||||
RequestID int64 `json:"reqid,omitempty"`
|
||||
}
|
||||
|
||||
// TestMain setup test
|
||||
func TestMain(m *testing.M) {
|
||||
wc = &WebsocketConnection{
|
||||
ExchangeName: "test",
|
||||
Verbose: true,
|
||||
URL: returnResponseURL,
|
||||
ResponseMaxLimit: 7000000000,
|
||||
ResponseCheckTimeout: 30000000,
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
// TestDial logic test
|
||||
func TestDial(t *testing.T) {
|
||||
var testCases = []testStruct{
|
||||
{Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
|
||||
{Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
|
||||
{Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
|
||||
}
|
||||
for i := 0; i < len(testCases); i++ {
|
||||
testData := &testCases[i]
|
||||
t.Run(testData.WC.ExchangeName, func(t *testing.T) {
|
||||
if testData.WC.ProxyURL != "" && !useProxyTests {
|
||||
t.Skip("Proxy testing not enabled, skipping")
|
||||
}
|
||||
err := testData.WC.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
if testData.Error != nil && err.Error() == testData.Error.Error() {
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendMessage logic test
|
||||
func TestSendMessage(t *testing.T) {
|
||||
var testCases = []testStruct{
|
||||
{Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
|
||||
{Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
|
||||
{Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}},
|
||||
}
|
||||
for i := 0; i < len(testCases); i++ {
|
||||
testData := &testCases[i]
|
||||
t.Run(testData.WC.ExchangeName, func(t *testing.T) {
|
||||
if testData.WC.ProxyURL != "" && !useProxyTests {
|
||||
t.Skip("Proxy testing not enabled, skipping")
|
||||
}
|
||||
err := testData.WC.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
if testData.Error != nil && err.Error() == testData.Error.Error() {
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = testData.WC.SendMessage("ping")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSendMessageWithResponse logic test
|
||||
func TestSendMessageWithResponse(t *testing.T) {
|
||||
if wc.ProxyURL != "" && !useProxyTests {
|
||||
t.Skip("Proxy testing not enabled, skipping")
|
||||
}
|
||||
err := wc.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go readMesages(wc, t)
|
||||
|
||||
request := testRequest{
|
||||
Event: "subscribe",
|
||||
Pairs: []string{currency.NewPairWithDelimiter("XBT", "USD", "/").String()},
|
||||
Subscription: testRequestData{
|
||||
Name: "ticker",
|
||||
},
|
||||
RequestID: wc.GenerateMessageID(true),
|
||||
}
|
||||
_, err = wc.SendMessageReturnResponse(request.RequestID, request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseBinaryResponse logic test
|
||||
func TestParseBinaryResponse(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
w := gzip.NewWriter(&b)
|
||||
w.Write([]byte("hello"))
|
||||
w.Close()
|
||||
resp, err := wc.parseBinaryResponse(b.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !strings.EqualFold(string(resp), "hello") {
|
||||
t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp))
|
||||
}
|
||||
|
||||
var b2 bytes.Buffer
|
||||
w2, err2 := flate.NewWriter(&b2, 1)
|
||||
if err2 != nil {
|
||||
t.Error(err2)
|
||||
}
|
||||
w2.Write([]byte("hello"))
|
||||
w2.Close()
|
||||
resp2, err3 := wc.parseBinaryResponse(b2.Bytes())
|
||||
if err3 != nil {
|
||||
t.Error(err3)
|
||||
}
|
||||
if !strings.EqualFold(string(resp2), "hello") {
|
||||
t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp2))
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddResponseWithID logic test
|
||||
func TestAddResponseWithID(t *testing.T) {
|
||||
wc.IDResponses = nil
|
||||
wc.AddResponseWithID(0, []byte("hi"))
|
||||
wc.AddResponseWithID(1, []byte("hi"))
|
||||
}
|
||||
|
||||
// readMesages helper func
|
||||
func readMesages(wc *WebsocketConnection, t *testing.T) {
|
||||
timer := time.NewTimer(20 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
return
|
||||
default:
|
||||
resp, err := wc.ReadMessage()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
var incoming testResponse
|
||||
err = common.JSONDecode(resp.Raw, &incoming)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if incoming.RequestID > 0 {
|
||||
wc.AddResponseWithID(incoming.RequestID, resp.Raw)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package wshandler
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -15,7 +15,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -86,6 +86,7 @@ func (z *ZB) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
z.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
z.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
z.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user configuration
|
||||
@@ -148,6 +149,13 @@ func (z *ZB) Setup(exch *config.ExchangeConfig) {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
z.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"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/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// Please supply you own test keys here for due diligence testing.
|
||||
|
||||
@@ -12,7 +12,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"
|
||||
)
|
||||
|
||||
@@ -95,7 +95,7 @@ func (z *ZB) WsHandleData() {
|
||||
z.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Timestamp: time.Unix(0, ticker.Date),
|
||||
Pair: currency.NewPairFromString(cPair[0]),
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Exchange: z.GetName(),
|
||||
ClosePrice: ticker.Data.Last,
|
||||
HighPrice: ticker.Data.High,
|
||||
@@ -111,8 +111,8 @@ func (z *ZB) WsHandleData() {
|
||||
}
|
||||
|
||||
var asks []orderbook.Item
|
||||
for _, askDepth := range depth.Asks {
|
||||
ask := askDepth.([]interface{})
|
||||
for i := range depth.Asks {
|
||||
ask := depth.Asks[i].([]interface{})
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: ask[1].(float64),
|
||||
Price: ask[0].(float64),
|
||||
@@ -120,8 +120,8 @@ func (z *ZB) WsHandleData() {
|
||||
}
|
||||
|
||||
var bids []orderbook.Item
|
||||
for _, bidDepth := range depth.Bids {
|
||||
bid := bidDepth.([]interface{})
|
||||
for i := range depth.Bids {
|
||||
bid := depth.Bids[i].([]interface{})
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: bid[1].(float64),
|
||||
Price: bid[0].(float64),
|
||||
@@ -133,11 +133,10 @@ func (z *ZB) WsHandleData() {
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = "SPOT"
|
||||
newOrderBook.AssetType = orderbook.Spot
|
||||
newOrderBook.Pair = cPair
|
||||
|
||||
err = z.Websocket.Orderbook.LoadSnapshot(&newOrderBook,
|
||||
z.GetName(),
|
||||
true)
|
||||
if err != nil {
|
||||
z.Websocket.DataHandler <- err
|
||||
@@ -146,7 +145,7 @@ func (z *ZB) WsHandleData() {
|
||||
|
||||
z.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: cPair,
|
||||
Asset: "SPOT",
|
||||
Asset: orderbook.Spot,
|
||||
Exchange: z.GetName(),
|
||||
}
|
||||
|
||||
@@ -167,7 +166,7 @@ func (z *ZB) WsHandleData() {
|
||||
z.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(0, t.Date),
|
||||
CurrencyPair: cPair,
|
||||
AssetType: "SPOT",
|
||||
AssetType: orderbook.Spot,
|
||||
Exchange: z.GetName(),
|
||||
EventTime: t.Date,
|
||||
Price: t.Price,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user