mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Merge branch 'master' into engine
This commit is contained in:
@@ -39,7 +39,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| Huobi.Hadax | Yes | Yes | NA |
|
||||
| ItBit | Yes | NA | No |
|
||||
| Kraken | Yes | Yes | NA |
|
||||
| LakeBTC | Yes | No | NA |
|
||||
| LakeBTC | Yes | Yes | NA |
|
||||
| LocalBitcoins | Yes | NA | NA |
|
||||
| OKCoin International | Yes | Yes | No |
|
||||
| OKEX | Yes | Yes | No |
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
### Current Features
|
||||
|
||||
+ REST Support
|
||||
+ Websocket Support
|
||||
|
||||
### How to enable
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| Huobi.Hadax | Yes | Yes | NA |
|
||||
| ItBit | Yes | NA | No |
|
||||
| Kraken | Yes | Yes | NA |
|
||||
| LakeBTC | Yes | No | NA |
|
||||
| LakeBTC | Yes | Yes | NA |
|
||||
| LocalBitcoins | Yes | NA | NA |
|
||||
| OKCoin International | Yes | Yes | No |
|
||||
| OKEX | Yes | Yes | No |
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() {
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
|
||||
{{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL
|
||||
{{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault
|
||||
{{.Variable}}.Websocket = wshandler.New()
|
||||
{{.Variable}}.Websocket = monitor.New()
|
||||
{{.Variable}}.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
{{.Variable}}.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
}
|
||||
|
||||
@@ -187,20 +187,20 @@ func ({{.Variable}} *{{.CapitalName}}) GetFeeByType(feeBuilder *exchange.FeeBuil
|
||||
|
||||
// SubscribeToWebsocketChannels appends to ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle subscribing
|
||||
func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []monitor.WebsocketChannelSubscription) error {
|
||||
{{.Variable}}.Websocket.SubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe
|
||||
// which lets websocket.manageSubscriptions handle unsubscribing
|
||||
func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error {
|
||||
func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []monitor.WebsocketChannelSubscription) error {
|
||||
{{.Variable}}.Websocket.UnubscribeToChannels(channels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a copied list of subscriptions
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) {
|
||||
func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]monitor.WebsocketChannelSubscription, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ const (
|
||||
configDefaultHTTPTimeout = time.Second * 15
|
||||
configDefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
|
||||
configDefaultWebsocketResponseMaxLimit = time.Second * 7
|
||||
configDefaultWebsocketOrderbookBufferLimit = 5
|
||||
configMaxAuthFailures = 3
|
||||
defaultNTPAllowedDifference = 50000000
|
||||
defaultNTPAllowedNegativeDifference = 50000000
|
||||
@@ -1024,7 +1025,11 @@ func (c *Config) CheckExchangeConfigValues() error {
|
||||
c.Exchanges[i].Name, configDefaultWebsocketResponseMaxLimit)
|
||||
c.Exchanges[i].WebsocketResponseMaxLimit = configDefaultWebsocketResponseMaxLimit
|
||||
}
|
||||
|
||||
if c.Exchanges[i].WebsocketOrderbookBufferLimit <= 0 {
|
||||
log.Warnf(log.ExchangeSys, "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.",
|
||||
c.Exchanges[i].Name, configDefaultWebsocketOrderbookBufferLimit)
|
||||
c.Exchanges[i].WebsocketOrderbookBufferLimit = configDefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
err := c.CheckPairConsistency(c.Exchanges[i].Name)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "Exchange %s: CheckPairConsistency error: %s\n", c.Exchanges[i].Name, err)
|
||||
|
||||
@@ -647,6 +647,7 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
|
||||
checkExchangeConfigValues.Exchanges[0].WebsocketResponseMaxLimit = 0
|
||||
checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout = 0
|
||||
checkExchangeConfigValues.Exchanges[0].WebsocketOrderbookBufferLimit = 0
|
||||
checkExchangeConfigValues.Exchanges[0].HTTPTimeout = 0
|
||||
err = checkExchangeConfigValues.CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
@@ -659,8 +660,8 @@ func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketResponseMaxLimit value", checkExchangeConfigValues.Exchanges[0].Name)
|
||||
}
|
||||
|
||||
if checkExchangeConfigValues.Exchanges[0].WebsocketResponseCheckTimeout == 0 {
|
||||
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketResponseCheckTimeout value", checkExchangeConfigValues.Exchanges[0].Name)
|
||||
if checkExchangeConfigValues.Exchanges[0].WebsocketOrderbookBufferLimit == 0 {
|
||||
t.Fatalf("Test failed. Expected exchange %s to have updated WebsocketOrderbookBufferLimit value", checkExchangeConfigValues.Exchanges[0].Name)
|
||||
}
|
||||
|
||||
if checkExchangeConfigValues.Exchanges[0].HTTPTimeout == 0 {
|
||||
|
||||
@@ -57,6 +57,7 @@ type ExchangeConfig struct {
|
||||
HTTPRateLimiter *HTTPRateLimitConfig `json:"httpRateLimiter,omitempty"`
|
||||
WebsocketResponseCheckTimeout time.Duration `json:"websocketResponseCheckTimeout"`
|
||||
WebsocketResponseMaxLimit time.Duration `json:"websocketResponseMaxLimit"`
|
||||
WebsocketOrderbookBufferLimit int `json:"websocketOrderbookBufferLimit"`
|
||||
ProxyAddress string `json:"proxyAddress,omitempty"`
|
||||
BaseCurrencies currency.Currencies `json:"baseCurrencies"`
|
||||
CurrencyPairs *currency.PairsManager `json:"currencyPairs"`
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ func (n *ntpManager) Start() (err error) {
|
||||
}()
|
||||
|
||||
log.Debugln(log.TimeMgr, "NTP manager starting...")
|
||||
if Bot.Config.NTPClient.Level == 0 {
|
||||
if Bot.Config.NTPClient.Level == 0 && *Bot.Config.Logging.Enabled {
|
||||
// Initial NTP check (prompts user on how we should proceed)
|
||||
n.inititalCheck = true
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// GetDefaultConfig returns a default exchange config for Alphapoint
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -14,111 +13,14 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
)
|
||||
|
||||
const (
|
||||
binanceDefaultWebsocketURL = "wss://stream.binance.com:9443"
|
||||
)
|
||||
|
||||
var lastUpdateID map[string]int64
|
||||
var m sync.Mutex
|
||||
|
||||
// SeedLocalCache seeds depth data
|
||||
func (b *Binance) SeedLocalCache(p currency.Pair) error {
|
||||
var newOrderBook orderbook.Base
|
||||
|
||||
formattedPair := b.FormatExchangeCurrency(p, asset.Spot)
|
||||
|
||||
orderbookNew, err := b.GetOrderBook(
|
||||
OrderBookDataRequestParams{
|
||||
Symbol: formattedPair.String(),
|
||||
Limit: 1000,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
if lastUpdateID == nil {
|
||||
lastUpdateID = make(map[string]int64)
|
||||
}
|
||||
|
||||
lastUpdateID[formattedPair.String()] = orderbookNew.LastUpdateID
|
||||
m.Unlock()
|
||||
|
||||
for _, bids := range orderbookNew.Bids {
|
||||
newOrderBook.Bids = append(newOrderBook.Bids,
|
||||
orderbook.Item{Amount: bids.Quantity, Price: bids.Price})
|
||||
}
|
||||
for _, Asks := range orderbookNew.Asks {
|
||||
newOrderBook.Asks = append(newOrderBook.Asks,
|
||||
orderbook.Item{Amount: Asks.Quantity, Price: Asks.Price})
|
||||
}
|
||||
|
||||
newOrderBook.Pair = currency.NewPairFromString(formattedPair.String())
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
|
||||
}
|
||||
|
||||
// UpdateLocalCache updates and returns the most recent iteration of the orderbook
|
||||
func (b *Binance) UpdateLocalCache(ob *WebsocketDepthStream) error {
|
||||
m.Lock()
|
||||
ID, ok := lastUpdateID[ob.Pair]
|
||||
if !ok {
|
||||
m.Unlock()
|
||||
return fmt.Errorf("%v - Unable to find lastUpdateID", b.Name)
|
||||
}
|
||||
|
||||
if ob.LastUpdateID+1 <= ID || ID >= ob.LastUpdateID+1 {
|
||||
// Drop update, out of order
|
||||
m.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
lastUpdateID[ob.Pair] = ob.LastUpdateID
|
||||
m.Unlock()
|
||||
|
||||
var updateBid, updateAsk []orderbook.Item
|
||||
|
||||
for _, bidsToUpdate := range ob.UpdateBids {
|
||||
var priceToBeUpdated orderbook.Item
|
||||
for i, bids := range bidsToUpdate.([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
priceToBeUpdated.Price, _ = strconv.ParseFloat(bids.(string), 64)
|
||||
case 1:
|
||||
priceToBeUpdated.Amount, _ = strconv.ParseFloat(bids.(string), 64)
|
||||
}
|
||||
}
|
||||
updateBid = append(updateBid, priceToBeUpdated)
|
||||
}
|
||||
|
||||
for _, asksToUpdate := range ob.UpdateAsks {
|
||||
var priceToBeUpdated orderbook.Item
|
||||
for i, asks := range asksToUpdate.([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
priceToBeUpdated.Price, _ = strconv.ParseFloat(asks.(string), 64)
|
||||
case 1:
|
||||
priceToBeUpdated.Amount, _ = strconv.ParseFloat(asks.(string), 64)
|
||||
}
|
||||
}
|
||||
updateAsk = append(updateAsk, priceToBeUpdated)
|
||||
}
|
||||
|
||||
updatedTime := time.Unix(ob.Timestamp, 0)
|
||||
currencyPair := currency.NewPairFromString(ob.Pair)
|
||||
|
||||
return b.Websocket.Orderbook.Update(updateBid,
|
||||
updateAsk,
|
||||
currencyPair,
|
||||
updatedTime,
|
||||
b.GetName(),
|
||||
asset.Spot)
|
||||
}
|
||||
|
||||
// WSConnect intiates a websocket connection
|
||||
func (b *Binance) WSConnect() error {
|
||||
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
|
||||
@@ -174,11 +76,9 @@ func (b *Binance) WSConnect() error {
|
||||
// WsHandleData handles websocket data from WsReadData
|
||||
func (b *Binance) WsHandleData() {
|
||||
b.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
b.Websocket.Wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
@@ -233,7 +133,7 @@ func (b *Binance) WsHandleData() {
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
Exchange: b.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: asset.Spot,
|
||||
Side: trade.EventType,
|
||||
}
|
||||
continue
|
||||
@@ -307,7 +207,7 @@ func (b *Binance) WsHandleData() {
|
||||
currencyPair := currency.NewPairFromString(depth.Pair)
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: "SPOT",
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
continue
|
||||
@@ -315,3 +215,71 @@ func (b *Binance) WsHandleData() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SeedLocalCache seeds depth data
|
||||
func (b *Binance) SeedLocalCache(p currency.Pair) error {
|
||||
var newOrderBook orderbook.Base
|
||||
formattedPair := b.FormatExchangeCurrency(p, asset.Spot)
|
||||
orderbookNew, err := b.GetOrderBook(
|
||||
OrderBookDataRequestParams{
|
||||
Symbol: formattedPair.String(),
|
||||
Limit: 1000,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range orderbookNew.Bids {
|
||||
newOrderBook.Bids = append(newOrderBook.Bids,
|
||||
orderbook.Item{Amount: orderbookNew.Bids[i].Quantity, Price: orderbookNew.Bids[i].Price})
|
||||
}
|
||||
for i := range orderbookNew.Asks {
|
||||
newOrderBook.Asks = append(newOrderBook.Asks,
|
||||
orderbook.Item{Amount: orderbookNew.Asks[i].Quantity, Price: orderbookNew.Asks[i].Price})
|
||||
}
|
||||
|
||||
newOrderBook.LastUpdated = time.Unix(orderbookNew.LastUpdateID, 0)
|
||||
newOrderBook.Pair = currency.NewPairFromString(formattedPair.String())
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
|
||||
return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
}
|
||||
|
||||
// UpdateLocalCache updates and returns the most recent iteration of the orderbook
|
||||
func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error {
|
||||
var updateBid, updateAsk []orderbook.Item
|
||||
for i := range wsdp.UpdateBids {
|
||||
var priceToBeUpdated orderbook.Item
|
||||
for i, bids := range wsdp.UpdateBids[i].([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
priceToBeUpdated.Price, _ = strconv.ParseFloat(bids.(string), 64)
|
||||
case 1:
|
||||
priceToBeUpdated.Amount, _ = strconv.ParseFloat(bids.(string), 64)
|
||||
}
|
||||
}
|
||||
updateBid = append(updateBid, priceToBeUpdated)
|
||||
}
|
||||
|
||||
for i := range wsdp.UpdateAsks {
|
||||
var priceToBeUpdated orderbook.Item
|
||||
for i, asks := range wsdp.UpdateAsks[i].([]interface{}) {
|
||||
switch i {
|
||||
case 0:
|
||||
priceToBeUpdated.Price, _ = strconv.ParseFloat(asks.(string), 64)
|
||||
case 1:
|
||||
priceToBeUpdated.Amount, _ = strconv.ParseFloat(asks.(string), 64)
|
||||
}
|
||||
}
|
||||
updateAsk = append(updateAsk, priceToBeUpdated)
|
||||
}
|
||||
currencyPair := currency.NewPairFromString(wsdp.Pair)
|
||||
|
||||
return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: updateBid,
|
||||
Asks: updateAsk,
|
||||
CurrencyPair: currencyPair,
|
||||
UpdateID: wsdp.LastUpdateID,
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -98,6 +98,7 @@ func (b *Binance) SetDefaults() {
|
||||
wshandler.WebsocketOrderbookSupported
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup takes in the supplied exchange configuration details and sets params
|
||||
@@ -133,6 +134,14 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -183,7 +184,11 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
|
||||
if stream.Type == websocket.TextMessage {
|
||||
var result interface{}
|
||||
common.JSONDecode(stream.Raw, &result)
|
||||
err = common.JSONDecode(stream.Raw, &result)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
switch reflect.TypeOf(result).String() {
|
||||
case "map[string]interface {}":
|
||||
eventData := result.(map[string]interface{})
|
||||
@@ -230,26 +235,19 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
switch chanInfo.Channel {
|
||||
case "book":
|
||||
var newOrderbook []WebsocketBook
|
||||
curr := currency.NewPairFromString(chanInfo.Pair)
|
||||
switch len(chanData) {
|
||||
case 2:
|
||||
data := chanData[1].([]interface{})
|
||||
for _, x := range data {
|
||||
y := x.([]interface{})
|
||||
for i := range data {
|
||||
y := data[i].([]interface{})
|
||||
newOrderbook = append(newOrderbook, WebsocketBook{
|
||||
Price: y[0].(float64),
|
||||
Count: int(y[1].(float64)),
|
||||
Amount: y[2].(float64)})
|
||||
}
|
||||
|
||||
case 4:
|
||||
newOrderbook = append(newOrderbook, WebsocketBook{
|
||||
Price: chanData[1].(float64),
|
||||
Count: int(chanData[2].(float64)),
|
||||
Amount: chanData[3].(float64)})
|
||||
}
|
||||
|
||||
if len(newOrderbook) > 1 {
|
||||
err := b.WsInsertSnapshot(currency.NewPairFromString(chanInfo.Pair),
|
||||
err := b.WsInsertSnapshot(curr,
|
||||
asset.Spot,
|
||||
newOrderbook)
|
||||
|
||||
@@ -257,18 +255,20 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s",
|
||||
err)
|
||||
}
|
||||
continue
|
||||
case 4:
|
||||
newOrderbook = append(newOrderbook, WebsocketBook{
|
||||
Price: chanData[1].(float64),
|
||||
Count: int(chanData[2].(float64)),
|
||||
Amount: chanData[3].(float64)})
|
||||
err := b.WsUpdateOrderbook(curr,
|
||||
asset.Spot,
|
||||
newOrderbook)
|
||||
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
err := b.WsUpdateOrderbook(currency.NewPairFromString(chanInfo.Pair),
|
||||
asset.Spot,
|
||||
newOrderbook[0])
|
||||
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s",
|
||||
err)
|
||||
}
|
||||
|
||||
case "ticker":
|
||||
b.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Quantity: chanData[8].(float64),
|
||||
@@ -285,8 +285,8 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
case bitfinexWebsocketPositionSnapshot:
|
||||
var positionSnapshot []WebsocketPosition
|
||||
data := chanData[2].([]interface{})
|
||||
for _, x := range data {
|
||||
y := x.([]interface{})
|
||||
for i := range data {
|
||||
y := data[i].([]interface{})
|
||||
positionSnapshot = append(positionSnapshot,
|
||||
WebsocketPosition{
|
||||
Pair: y[0].(string),
|
||||
@@ -318,8 +318,8 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
case bitfinexWebsocketWalletSnapshot:
|
||||
data := chanData[2].([]interface{})
|
||||
var walletSnapshot []WebsocketWallet
|
||||
for _, x := range data {
|
||||
y := x.([]interface{})
|
||||
for i := range data {
|
||||
y := data[i].([]interface{})
|
||||
walletSnapshot = append(walletSnapshot,
|
||||
WebsocketWallet{
|
||||
Name: y[0].(string),
|
||||
@@ -343,8 +343,8 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
case bitfinexWebsocketOrderSnapshot:
|
||||
var orderSnapshot []WebsocketOrder
|
||||
data := chanData[2].([]interface{})
|
||||
for _, x := range data {
|
||||
y := x.([]interface{})
|
||||
for i := range data {
|
||||
y := data[i].([]interface{})
|
||||
orderSnapshot = append(orderSnapshot,
|
||||
WebsocketOrder{
|
||||
OrderID: int64(y[0].(float64)),
|
||||
@@ -407,8 +407,8 @@ func (b *Bitfinex) WsDataHandler() {
|
||||
switch len(chanData) {
|
||||
case 2:
|
||||
data := chanData[1].([]interface{})
|
||||
for _, x := range data {
|
||||
y := x.([]interface{})
|
||||
for i := range data {
|
||||
y := data[i].([]interface{})
|
||||
if _, ok := y[0].(string); ok {
|
||||
continue
|
||||
}
|
||||
@@ -463,31 +463,26 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books
|
||||
if len(books) == 0 {
|
||||
return errors.New("bitfinex.go error - no orderbooks submitted")
|
||||
}
|
||||
|
||||
var bid, ask []orderbook.Item
|
||||
for _, book := range books {
|
||||
if book.Amount >= 0 {
|
||||
bid = append(bid, orderbook.Item{Amount: book.Amount, Price: book.Price})
|
||||
for i := range books {
|
||||
if books[i].Amount >= 0 {
|
||||
bid = append(bid, orderbook.Item{Amount: books[i].Amount, Price: books[i].Price})
|
||||
} else {
|
||||
ask = append(ask, orderbook.Item{Amount: book.Amount * -1, Price: book.Price})
|
||||
ask = append(ask, orderbook.Item{Amount: books[i].Amount * -1, Price: books[i].Price})
|
||||
}
|
||||
}
|
||||
|
||||
if len(bid) == 0 && len(ask) == 0 {
|
||||
return errors.New("bitfinex.go error - no orderbooks in item lists")
|
||||
}
|
||||
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = ask
|
||||
newOrderBook.AssetType = assetType
|
||||
newOrderBook.Bids = bid
|
||||
newOrderBook.Pair = p
|
||||
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitfinex.go error - %s", err)
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName()}
|
||||
@@ -496,80 +491,36 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books
|
||||
|
||||
// WsUpdateOrderbook updates the orderbook list, removing and adding to the
|
||||
// orderbook sides
|
||||
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book WebsocketBook) error {
|
||||
func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book []WebsocketBook) error {
|
||||
orderbookUpdate := wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: []orderbook.Item{},
|
||||
Bids: []orderbook.Item{},
|
||||
AssetType: assetType,
|
||||
CurrencyPair: p,
|
||||
}
|
||||
|
||||
if book.Count > 0 {
|
||||
if book.Amount > 0 {
|
||||
// Update/add bid
|
||||
newBidPrice := orderbook.Item{Price: book.Price, Amount: book.Amount}
|
||||
err := b.Websocket.Orderbook.Update([]orderbook.Item{newBidPrice},
|
||||
nil,
|
||||
p,
|
||||
time.Now(),
|
||||
b.GetName(),
|
||||
assetType)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
for i := 0; i < len(book); i++ {
|
||||
switch {
|
||||
case book[i].Count > 0:
|
||||
if book[i].Amount > 0 {
|
||||
// update bid
|
||||
orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: book[i].Amount, Price: book[i].Price})
|
||||
} else if book[i].Amount < 0 {
|
||||
// update ask
|
||||
orderbookUpdate.Asks = append(orderbookUpdate.Asks, orderbook.Item{Amount: book[i].Amount * -1, Price: book[i].Price})
|
||||
}
|
||||
case book[i].Count == 0:
|
||||
if book[i].Amount == 1 {
|
||||
// delete bid
|
||||
orderbookUpdate.Bids = append(orderbookUpdate.Bids, orderbook.Item{Amount: 0, Price: book[i].Price})
|
||||
} else if book[i].Amount == -1 {
|
||||
// delete ask
|
||||
orderbookUpdate.Asks = append(orderbookUpdate.Asks, orderbook.Item{Amount: 0, Price: book[i].Price})
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName()}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update/add ask
|
||||
newAskPrice := orderbook.Item{Price: book.Price, Amount: book.Amount * -1}
|
||||
err := b.Websocket.Orderbook.Update(nil,
|
||||
[]orderbook.Item{newAskPrice},
|
||||
p,
|
||||
time.Now(),
|
||||
b.GetName(),
|
||||
assetType)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName()}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if book.Amount == 1 {
|
||||
// Remove bid
|
||||
bidPriceRemove := orderbook.Item{Price: book.Price, Amount: 0}
|
||||
err := b.Websocket.Orderbook.Update([]orderbook.Item{bidPriceRemove},
|
||||
nil,
|
||||
p,
|
||||
time.Now(),
|
||||
b.GetName(),
|
||||
assetType)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName()}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove from ask
|
||||
askPriceRemove := orderbook.Item{Price: book.Price, Amount: 0}
|
||||
err := b.Websocket.Orderbook.Update(nil,
|
||||
[]orderbook.Item{askPriceRemove},
|
||||
p,
|
||||
time.Now(),
|
||||
b.GetName(),
|
||||
assetType)
|
||||
|
||||
orderbookUpdate.UpdateTime = time.Now()
|
||||
err := b.Websocket.Orderbook.Update(&orderbookUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -99,6 +99,7 @@ func (b *Bitfinex) SetDefaults() {
|
||||
wshandler.WebsocketAuthenticatedEndpointsSupported
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup takes in the supplied exchange configuration details and sets params
|
||||
@@ -134,6 +135,14 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -74,31 +74,27 @@ func (b *Bithumb) GetTradablePairs() ([]string, error) {
|
||||
//
|
||||
// symbol e.g. "btc"
|
||||
func (b *Bithumb) GetTicker(symbol string) (Ticker, error) {
|
||||
response := Ticker{}
|
||||
path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, strings.ToUpper(symbol))
|
||||
|
||||
var response TickerResponse
|
||||
path := fmt.Sprintf("%s%s%s",
|
||||
b.API.Endpoints.URL,
|
||||
publicTicker,
|
||||
strings.ToUpper(symbol))
|
||||
err := b.SendHTTPRequest(path, &response)
|
||||
if err != nil {
|
||||
return response, err
|
||||
return response.Data, err
|
||||
}
|
||||
|
||||
if response.Status != noError {
|
||||
return response, errors.New(response.Message)
|
||||
return response.Data, errors.New(response.Message)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
return response.Data, nil
|
||||
}
|
||||
|
||||
// GetAllTickers returns all ticker information
|
||||
func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) {
|
||||
type Response struct {
|
||||
ActionStatus
|
||||
Data map[string]interface{}
|
||||
}
|
||||
|
||||
response := Response{}
|
||||
var response TickersResponse
|
||||
path := fmt.Sprintf("%s%s%s", b.API.Endpoints.URL, publicTicker, "all")
|
||||
|
||||
err := b.SendHTTPRequest(path, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -113,25 +109,12 @@ func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) {
|
||||
if k == "date" {
|
||||
continue
|
||||
}
|
||||
|
||||
if reflect.TypeOf(v).String() != "map[string]interface {}" {
|
||||
continue
|
||||
var newTicker Ticker
|
||||
err := common.JSONDecode(v, &newTicker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := v.(map[string]interface{})
|
||||
var t Ticker
|
||||
t.AveragePrice, _ = strconv.ParseFloat(data["average_price"].(string), 64)
|
||||
t.BuyPrice, _ = strconv.ParseFloat(data["buy_price"].(string), 64)
|
||||
t.ClosingPrice, _ = strconv.ParseFloat(data["closing_price"].(string), 64)
|
||||
t.MaxPrice, _ = strconv.ParseFloat(data["max_price"].(string), 64)
|
||||
t.MinPrice, _ = strconv.ParseFloat(data["min_price"].(string), 64)
|
||||
t.OpeningPrice, _ = strconv.ParseFloat(data["opening_price"].(string), 64)
|
||||
t.SellPrice, _ = strconv.ParseFloat(data["sell_price"].(string), 64)
|
||||
t.UnitsTraded, _ = strconv.ParseFloat(data["units_traded"].(string), 64)
|
||||
t.Volume1Day, _ = strconv.ParseFloat(data["volume_1day"].(string), 64)
|
||||
t.Volume7Day, _ = strconv.ParseFloat(data["volume_7day"].(string), 64)
|
||||
result[k] = t
|
||||
|
||||
result[k] = newTicker
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -527,7 +510,7 @@ func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, r
|
||||
|
||||
err = common.JSONDecode(intermediary, &errCapture)
|
||||
if err == nil {
|
||||
if errCapture.Status != "" && errCapture.Status != "0000" {
|
||||
if errCapture.Status != "" && errCapture.Status != noError {
|
||||
return fmt.Errorf("sendAuthenticatedAPIRequest error code: %s message:%s",
|
||||
errCapture.Status,
|
||||
errCode[errCapture.Status])
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
package bithumb
|
||||
|
||||
import "github.com/thrasher-corp/gocryptotrader/currency"
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
// Ticker holds ticker data
|
||||
type Ticker struct {
|
||||
OpeningPrice float64 `json:"opening_price,string"`
|
||||
ClosingPrice float64 `json:"closing_price,string"`
|
||||
MinPrice float64 `json:"min_price,string"`
|
||||
MaxPrice float64 `json:"max_price,string"`
|
||||
AveragePrice float64 `json:"average_price,string"`
|
||||
UnitsTraded float64 `json:"units_traded,string"`
|
||||
Volume1Day float64 `json:"volume_1day,string"`
|
||||
Volume7Day float64 `json:"volume_7day,string"`
|
||||
BuyPrice float64 `json:"buy_price,string"`
|
||||
SellPrice float64 `json:"sell_price,string"`
|
||||
ActionStatus
|
||||
// Date int64 `json:"date,string"`
|
||||
OpeningPrice float64 `json:"opening_price,string"`
|
||||
ClosingPrice float64 `json:"closing_price,string"`
|
||||
MinPrice float64 `json:"min_price,string"`
|
||||
MaxPrice float64 `json:"max_price,string"`
|
||||
UnitsTraded float64 `json:"units_traded,string"`
|
||||
AccumulatedTradeValue float64 `json:"acc_trade_value,string"`
|
||||
PreviousClosingPrice float64 `json:"prev_closing_price,string"`
|
||||
UnitsTraded24Hr float64 `json:"units_traded_24H,string"`
|
||||
AccumulatedTradeValue24hr float64 `json:"acc_trade_value_24H,string"`
|
||||
Fluctate24Hr string `json:"fluctate_24H"`
|
||||
FluctateRate24hr float64 `json:"fluctate_rate_24H,string"`
|
||||
Date int64 `json:"date,string"`
|
||||
}
|
||||
|
||||
// TickerResponse holds the standard ticker response
|
||||
@@ -27,9 +31,9 @@ type TickerResponse struct {
|
||||
|
||||
// TickersResponse holds the standard ticker response
|
||||
type TickersResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data map[string]Ticker `json:"data"`
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
Data map[string]json.RawMessage `json:"data"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Orderbook holds full range of order book information
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -164,11 +164,9 @@ func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (ticker.Pr
|
||||
currency := x.Base.String()
|
||||
var tp ticker.Price
|
||||
tp.Pair = x
|
||||
tp.Ask = tickers[currency].SellPrice
|
||||
tp.Bid = tickers[currency].BuyPrice
|
||||
tp.Low = tickers[currency].MinPrice
|
||||
tp.Last = tickers[currency].ClosingPrice
|
||||
tp.Volume = tickers[currency].Volume1Day
|
||||
tp.Volume = tickers[currency].UnitsTraded24Hr
|
||||
tp.High = tickers[currency].MaxPrice
|
||||
|
||||
err = ticker.ProcessTicker(b.Name, &tp, assetType)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// Bitmex is the overarching type across this package
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -222,23 +223,22 @@ func (b *Bitmex) wsHandleIncomingData() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, trade := range trades.Data {
|
||||
for i := range trades.Data {
|
||||
var timestamp time.Time
|
||||
timestamp, err = time.Parse(time.RFC3339, trade.Timestamp)
|
||||
timestamp, err = time.Parse(time.RFC3339, trades.Data[i].Timestamp)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: update this to support multiple asset types
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: timestamp,
|
||||
Price: trade.Price,
|
||||
Amount: float64(trade.Size),
|
||||
CurrencyPair: currency.NewPairFromString(trade.Symbol),
|
||||
Price: trades.Data[i].Price,
|
||||
Amount: float64(trades.Data[i].Size),
|
||||
CurrencyPair: currency.NewPairFromString(trades.Data[i].Symbol),
|
||||
Exchange: b.GetName(),
|
||||
AssetType: "CONTRACT",
|
||||
Side: trade.Side,
|
||||
Side: trades.Data[i].Side,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,99 +328,80 @@ func (b *Bitmex) wsHandleIncomingData() {
|
||||
}
|
||||
}
|
||||
|
||||
var snapshotloaded = make(map[currency.Pair]map[asset.Item]bool)
|
||||
|
||||
// ProcessOrderbook processes orderbook updates
|
||||
func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType asset.Item) error { // nolint: unparam
|
||||
if len(data) < 1 {
|
||||
return errors.New("bitmex_websocket.go error - no orderbook data")
|
||||
}
|
||||
|
||||
_, ok := snapshotloaded[currencyPair]
|
||||
if !ok {
|
||||
snapshotloaded[currencyPair] = make(map[asset.Item]bool)
|
||||
}
|
||||
|
||||
_, ok = snapshotloaded[currencyPair][assetType]
|
||||
if !ok {
|
||||
snapshotloaded[currencyPair][assetType] = false
|
||||
}
|
||||
|
||||
switch action {
|
||||
case bitmexActionInitialData:
|
||||
if !snapshotloaded[currencyPair][assetType] {
|
||||
var newOrderBook orderbook.Base
|
||||
var bids, asks []orderbook.Item
|
||||
|
||||
for _, orderbookItem := range data {
|
||||
if strings.EqualFold(orderbookItem.Side, exchange.SellOrderSide.ToString()) {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Price: orderbookItem.Price,
|
||||
Amount: float64(orderbookItem.Size),
|
||||
})
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Price: orderbookItem.Price,
|
||||
Amount: float64(orderbookItem.Size),
|
||||
var newOrderBook orderbook.Base
|
||||
var bids, asks []orderbook.Item
|
||||
for i := range data {
|
||||
if strings.EqualFold(data[i].Side, exchange.SellOrderSide.ToString()) {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(bids) == 0 || len(asks) == 0 {
|
||||
return errors.New("bitmex_websocket.go error - snapshot not initialised correctly")
|
||||
}
|
||||
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = assetType
|
||||
newOrderBook.Pair = currencyPair
|
||||
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitmex_websocket.go process orderbook error - %s",
|
||||
err)
|
||||
}
|
||||
snapshotloaded[currencyPair][assetType] = true
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
})
|
||||
}
|
||||
|
||||
if len(bids) == 0 || len(asks) == 0 {
|
||||
return errors.New("bitmex_websocket.go error - snapshot not initialised correctly")
|
||||
}
|
||||
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = assetType
|
||||
newOrderBook.Pair = currencyPair
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bitmex_websocket.go process orderbook error - %s",
|
||||
err)
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
default:
|
||||
if snapshotloaded[currencyPair][assetType] {
|
||||
var asks, bids []orderbook.Item
|
||||
for _, orderbookItem := range data {
|
||||
if orderbookItem.Side == "Sell" {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Price: orderbookItem.Price,
|
||||
Amount: float64(orderbookItem.Size),
|
||||
})
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Price: orderbookItem.Price,
|
||||
Amount: float64(orderbookItem.Size),
|
||||
var asks, bids []orderbook.Item
|
||||
for i := range data {
|
||||
if strings.EqualFold(data[i].Side, "Sell") {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
})
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{
|
||||
Price: data[i].Price,
|
||||
Amount: float64(data[i].Size),
|
||||
})
|
||||
}
|
||||
|
||||
err := b.Websocket.Orderbook.UpdateUsingID(bids,
|
||||
asks,
|
||||
currencyPair,
|
||||
b.GetName(),
|
||||
assetType,
|
||||
action)
|
||||
err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: currencyPair,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: assetType,
|
||||
Action: action,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: currencyPair,
|
||||
Asset: assetType,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -123,6 +123,7 @@ func (b *Bitmex) SetDefaults() {
|
||||
wshandler.WebsocketDeadMansSwitchSupported
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup takes in the supplied exchange configuration details and sets params
|
||||
@@ -158,6 +159,14 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -157,22 +157,21 @@ func (b *Bitstamp) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubs
|
||||
return b.WebsocketConn.SendMessage(req)
|
||||
}
|
||||
|
||||
func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, assetType asset.Item) error {
|
||||
if len(ob.Asks) == 0 && len(ob.Bids) == 0 {
|
||||
func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, assetType asset.Item) error {
|
||||
if len(update.Asks) == 0 && len(update.Bids) == 0 {
|
||||
return errors.New("bitstamp_websocket.go error - no orderbook data")
|
||||
}
|
||||
|
||||
var asks, bids []orderbook.Item
|
||||
|
||||
if len(ob.Asks) > 0 {
|
||||
for _, ask := range ob.Asks {
|
||||
target, err := strconv.ParseFloat(ask[0], 64)
|
||||
if len(update.Asks) > 0 {
|
||||
for i := range update.Asks {
|
||||
target, err := strconv.ParseFloat(update.Asks[i][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(ask[1], 64)
|
||||
amount, err := strconv.ParseFloat(update.Asks[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
@@ -182,15 +181,15 @@ func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, ass
|
||||
}
|
||||
}
|
||||
|
||||
if len(ob.Bids) > 0 {
|
||||
for _, bid := range ob.Bids {
|
||||
target, err := strconv.ParseFloat(bid[0], 64)
|
||||
if len(update.Bids) > 0 {
|
||||
for i := range update.Bids {
|
||||
target, err := strconv.ParseFloat(update.Bids[i][0], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(bid[1], 64)
|
||||
amount, err := strconv.ParseFloat(update.Bids[i][1], 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
@@ -199,8 +198,13 @@ func (b *Bitstamp) wsUpdateOrderbook(ob websocketOrderBook, p currency.Pair, ass
|
||||
bids = append(bids, orderbook.Item{Price: target, Amount: amount})
|
||||
}
|
||||
}
|
||||
|
||||
err := b.Websocket.Orderbook.Update(bids, asks, p, time.Now(), b.GetName(), assetType)
|
||||
err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: p,
|
||||
UpdateID: update.Timestamp,
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -225,17 +229,17 @@ func (b *Bitstamp) seedOrderBook() error {
|
||||
var newOrderBook orderbook.Base
|
||||
var asks, bids []orderbook.Item
|
||||
|
||||
for _, ask := range orderbookSeed.Asks {
|
||||
for i := range orderbookSeed.Asks {
|
||||
var item orderbook.Item
|
||||
item.Amount = ask.Amount
|
||||
item.Price = ask.Price
|
||||
item.Amount = orderbookSeed.Asks[i].Amount
|
||||
item.Price = orderbookSeed.Asks[i].Price
|
||||
asks = append(asks, item)
|
||||
}
|
||||
|
||||
for _, bid := range orderbookSeed.Bids {
|
||||
for i := range orderbookSeed.Bids {
|
||||
var item orderbook.Item
|
||||
item.Amount = bid.Amount
|
||||
item.Price = bid.Price
|
||||
item.Amount = orderbookSeed.Bids[i].Amount
|
||||
item.Price = orderbookSeed.Bids[i].Price
|
||||
bids = append(bids, item)
|
||||
}
|
||||
|
||||
@@ -244,7 +248,7 @@ func (b *Bitstamp) seedOrderBook() error {
|
||||
newOrderBook.Pair = p[x]
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, b.GetName(), false)
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -96,6 +96,7 @@ func (b *Bitstamp) SetDefaults() {
|
||||
wshandler.WebsocketUnsubscribeSupported
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets configuration values to bitstamp
|
||||
@@ -131,6 +132,14 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package btse
|
||||
|
||||
import "time"
|
||||
|
||||
// Market stores market data
|
||||
type Market struct {
|
||||
ID string `json:"id"`
|
||||
Symbol string `json:"symbol"`
|
||||
BaseCurrency string `json:"base_currency"`
|
||||
QuoteCurrency string `json:"quote_currency"`
|
||||
BaseMinSize float64 `json:"base_min_size"`
|
||||
@@ -51,8 +53,8 @@ type MarketStatistics struct {
|
||||
|
||||
// ServerTime stores the server time data
|
||||
type ServerTime struct {
|
||||
ISO string `json:"iso"`
|
||||
Epoch float64 `json:"epoch"`
|
||||
ISO time.Time `json:"iso"`
|
||||
Epoch string `json:"epoch"`
|
||||
}
|
||||
|
||||
// CurrencyBalance stores the account info data
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -64,7 +64,7 @@ func (b *BTSE) WsHandleData() {
|
||||
ProductID string `json:"product_id"`
|
||||
}
|
||||
|
||||
if strings.Contains(string(resp.Raw), "Welcome to BTSE") {
|
||||
if strings.Contains(string(resp.Raw), "Connected. Welcome to BTSE!") {
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s websocket client successfully connected to %s",
|
||||
b.Name, b.Websocket.GetWebsocketURL())
|
||||
@@ -122,14 +122,14 @@ func (b *BTSE) WsHandleData() {
|
||||
// ProcessSnapshot processes the initial orderbook snap shot
|
||||
func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
|
||||
var base orderbook.Base
|
||||
for _, bid := range snapshot.Bids {
|
||||
p := strings.Replace(bid[0].(string), ",", "", -1)
|
||||
for i := range snapshot.Bids {
|
||||
p := strings.Replace(snapshot.Bids[i][0].(string), ",", "", -1)
|
||||
price, err := strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := strings.Replace(bid[1].(string), ",", "", -1)
|
||||
a := strings.Replace(snapshot.Bids[i][1].(string), ",", "", -1)
|
||||
amount, err := strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -139,14 +139,14 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
|
||||
orderbook.Item{Price: price, Amount: amount})
|
||||
}
|
||||
|
||||
for _, ask := range snapshot.Asks {
|
||||
p := strings.Replace(ask[0].(string), ",", "", -1)
|
||||
for i := range snapshot.Asks {
|
||||
p := strings.Replace(snapshot.Asks[i][0].(string), ",", "", -1)
|
||||
price, err := strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := strings.Replace(ask[1].(string), ",", "", -1)
|
||||
a := strings.Replace(snapshot.Asks[i][1].(string), ",", "", -1)
|
||||
amount, err := strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -162,7 +162,7 @@ func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
|
||||
base.LastUpdated = time.Now()
|
||||
base.ExchangeName = b.Name
|
||||
|
||||
err := base.Process()
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&base, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -93,6 +93,7 @@ func (b *BTSE) SetDefaults() {
|
||||
wshandler.WebsocketUnsubscribeSupported
|
||||
b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
|
||||
}
|
||||
|
||||
@@ -129,6 +130,14 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
b.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -166,7 +175,10 @@ func (b *BTSE) FetchTradablePairs(asset asset.Item) ([]string, error) {
|
||||
|
||||
var pairs []string
|
||||
for _, m := range *markets {
|
||||
pairs = append(pairs, m.ID)
|
||||
if m.Status != "active" {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, m.Symbol)
|
||||
}
|
||||
|
||||
return pairs, nil
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -179,13 +180,13 @@ func (c *CoinbasePro) WsHandleData() {
|
||||
// ProcessSnapshot processes the initial orderbook snap shot
|
||||
func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) error {
|
||||
var base orderbook.Base
|
||||
for _, bid := range snapshot.Bids {
|
||||
price, err := strconv.ParseFloat(bid[0].(string), 64)
|
||||
for i := range snapshot.Bids {
|
||||
price, err := strconv.ParseFloat(snapshot.Bids[i][0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(bid[1].(string), 64)
|
||||
amount, err := strconv.ParseFloat(snapshot.Bids[i][1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -194,13 +195,13 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
|
||||
orderbook.Item{Price: price, Amount: amount})
|
||||
}
|
||||
|
||||
for _, ask := range snapshot.Asks {
|
||||
price, err := strconv.ParseFloat(ask[0].(string), 64)
|
||||
for i := range snapshot.Asks {
|
||||
price, err := strconv.ParseFloat(snapshot.Asks[i][0].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amount, err := strconv.ParseFloat(ask[1].(string), 64)
|
||||
amount, err := strconv.ParseFloat(snapshot.Asks[i][1].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -209,18 +210,17 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
|
||||
orderbook.Item{Price: price, Amount: amount})
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(snapshot.ProductID)
|
||||
pair := currency.NewPairFromString(snapshot.ProductID)
|
||||
base.AssetType = asset.Spot
|
||||
base.Pair = p
|
||||
base.LastUpdated = time.Now()
|
||||
base.Pair = pair
|
||||
|
||||
err := c.Websocket.Orderbook.LoadSnapshot(&base, c.GetName(), false)
|
||||
err := c.Websocket.Orderbook.LoadSnapshot(&base, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Pair: pair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: c.GetName(),
|
||||
}
|
||||
@@ -230,26 +230,35 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro
|
||||
|
||||
// ProcessUpdate updates the orderbook local cache
|
||||
func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error {
|
||||
var Asks, Bids []orderbook.Item
|
||||
var asks, bids []orderbook.Item
|
||||
|
||||
for _, data := range update.Changes {
|
||||
price, _ := strconv.ParseFloat(data[1].(string), 64)
|
||||
volume, _ := strconv.ParseFloat(data[2].(string), 64)
|
||||
for i := range update.Changes {
|
||||
price, _ := strconv.ParseFloat(update.Changes[i][1].(string), 64)
|
||||
volume, _ := strconv.ParseFloat(update.Changes[i][2].(string), 64)
|
||||
|
||||
if data[0].(string) == exchange.BuyOrderSide.ToLower().ToString() {
|
||||
Bids = append(Bids, orderbook.Item{Price: price, Amount: volume})
|
||||
if update.Changes[i][0].(string) == exchange.BuyOrderSide.ToLower().ToString() {
|
||||
bids = append(bids, orderbook.Item{Price: price, Amount: volume})
|
||||
} else {
|
||||
Asks = append(Asks, orderbook.Item{Price: price, Amount: volume})
|
||||
asks = append(asks, orderbook.Item{Price: price, Amount: volume})
|
||||
}
|
||||
}
|
||||
|
||||
if len(Asks) == 0 && len(Bids) == 0 {
|
||||
return errors.New("coibasepro_websocket.go error - no data in websocket update")
|
||||
if len(asks) == 0 && len(bids) == 0 {
|
||||
return errors.New("coinbasepro_websocket.go error - no data in websocket update")
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(update.ProductID)
|
||||
|
||||
err := c.Websocket.Orderbook.Update(Bids, Asks, p, time.Now(), c.GetName(), asset.Spot)
|
||||
timestamp, err := time.Parse(time.RFC3339, update.Time)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: p,
|
||||
UpdateTime: timestamp,
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -99,6 +99,7 @@ func (c *CoinbasePro) SetDefaults() {
|
||||
wshandler.WebsocketSequenceNumberSupported
|
||||
c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup initialises the exchange parameters with the current configuration
|
||||
@@ -134,6 +135,14 @@ func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
c.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
var c COINUT
|
||||
|
||||
@@ -14,7 +14,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
)
|
||||
|
||||
const coinutWebsocketURL = "wss://wsapi.coinut.com"
|
||||
@@ -257,18 +258,18 @@ func (c *COINUT) WsSetInstrumentList() error {
|
||||
// WsProcessOrderbookSnapshot processes the orderbook snapshot
|
||||
func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
|
||||
var bids []orderbook.Item
|
||||
for _, bid := range ob.Buy {
|
||||
for i := range ob.Buy {
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: bid.Volume,
|
||||
Price: bid.Price,
|
||||
Amount: ob.Buy[i].Volume,
|
||||
Price: ob.Buy[i].Price,
|
||||
})
|
||||
}
|
||||
|
||||
var asks []orderbook.Item
|
||||
for _, ask := range ob.Sell {
|
||||
for i := range ob.Sell {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: ask.Volume,
|
||||
Price: ask.Price,
|
||||
Amount: ob.Sell[i].Volume,
|
||||
Price: ob.Sell[i].Price,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,32 +278,24 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error {
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.Pair = currency.NewPairFromString(instrumentListByCode[ob.InstID])
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.LastUpdated = time.Now()
|
||||
|
||||
return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, c.GetName(), false)
|
||||
return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
}
|
||||
|
||||
// WsProcessOrderbookUpdate process an orderbook update
|
||||
func (c *COINUT) WsProcessOrderbookUpdate(ob *WsOrderbookUpdate) error {
|
||||
p := currency.NewPairFromString(instrumentListByCode[ob.InstID])
|
||||
|
||||
if ob.Side == exchange.BuyOrderSide.ToLower().ToString() {
|
||||
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
||||
{Price: ob.Price, Amount: ob.Volume}},
|
||||
nil,
|
||||
p,
|
||||
time.Now(),
|
||||
c.GetName(),
|
||||
asset.Spot)
|
||||
func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error {
|
||||
p := currency.NewPairFromString(instrumentListByCode[update.InstID])
|
||||
bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{
|
||||
CurrencyPair: p,
|
||||
UpdateID: update.TransID,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
|
||||
return c.Websocket.Orderbook.Update([]orderbook.Item{
|
||||
{Price: ob.Price, Amount: ob.Volume}},
|
||||
nil,
|
||||
p,
|
||||
time.Now(),
|
||||
c.GetName(),
|
||||
asset.Spot)
|
||||
if strings.EqualFold(update.Side, exchange.BuyOrderSide.ToLower().ToString()) {
|
||||
bufferUpdate.Bids = []orderbook.Item{{Price: update.Price, Amount: update.Volume}}
|
||||
} else {
|
||||
bufferUpdate.Asks = []orderbook.Item{{Price: update.Price, Amount: update.Volume}}
|
||||
}
|
||||
return c.Websocket.Orderbook.Update(bufferUpdate)
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -99,6 +99,7 @@ func (c *COINUT) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets the current exchange configuration
|
||||
@@ -134,6 +135,14 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
c.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ const (
|
||||
DefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
|
||||
// DefaultWebsocketResponseMaxLimit is the default max wait for an expected websocket response before a timeout
|
||||
DefaultWebsocketResponseMaxLimit = time.Second * 7
|
||||
// DefaultWebsocketOrderbookBufferLimit is the maximum number of orderbook updates that get stored before being applied
|
||||
DefaultWebsocketOrderbookBufferLimit = 5
|
||||
)
|
||||
|
||||
func (e *Base) checkAndInitRequester() {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// Endpoint authentication types
|
||||
@@ -313,6 +313,7 @@ type Base struct {
|
||||
HTTPDebugging bool
|
||||
WebsocketResponseCheckTimeout time.Duration
|
||||
WebsocketResponseMaxLimit time.Duration
|
||||
WebsocketOrderbookBufferLimit int64
|
||||
Websocket *wshandler.Websocket
|
||||
*request.Requester
|
||||
Config *config.ExchangeConfig
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -161,15 +162,15 @@ func (g *Gateio) WsHandleData() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, trade := range trades {
|
||||
for i := range trades {
|
||||
g.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Now(),
|
||||
CurrencyPair: currency.NewPairFromString(c),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: g.GetName(),
|
||||
Price: trade.Price,
|
||||
Amount: trade.Amount,
|
||||
Side: trade.Type,
|
||||
Price: trades[i].Price,
|
||||
Amount: trades[i].Amount,
|
||||
Side: trades[i].Type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,9 +199,9 @@ func (g *Gateio) WsHandleData() {
|
||||
var asks, bids []orderbook.Item
|
||||
|
||||
askData, askOk := data["asks"]
|
||||
for _, ask := range askData {
|
||||
amount, _ := strconv.ParseFloat(ask[1], 64)
|
||||
price, _ := strconv.ParseFloat(ask[0], 64)
|
||||
for i := range askData {
|
||||
amount, _ := strconv.ParseFloat(askData[i][1], 64)
|
||||
price, _ := strconv.ParseFloat(askData[i][0], 64)
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
@@ -208,9 +209,9 @@ func (g *Gateio) WsHandleData() {
|
||||
}
|
||||
|
||||
bidData, bidOk := data["bids"]
|
||||
for _, bid := range bidData {
|
||||
amount, _ := strconv.ParseFloat(bid[1], 64)
|
||||
price, _ := strconv.ParseFloat(bid[0], 64)
|
||||
for i := range bidData {
|
||||
amount, _ := strconv.ParseFloat(bidData[i][1], 64)
|
||||
price, _ := strconv.ParseFloat(bidData[i][0], 64)
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
@@ -234,22 +235,22 @@ func (g *Gateio) WsHandleData() {
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.LastUpdated = time.Now()
|
||||
newOrderBook.Pair = currency.NewPairFromString(c)
|
||||
|
||||
err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook,
|
||||
g.GetName(),
|
||||
false)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
} else {
|
||||
err = g.Websocket.Orderbook.Update(asks,
|
||||
bids,
|
||||
currency.NewPairFromString(c),
|
||||
time.Now(),
|
||||
g.GetName(),
|
||||
asset.Spot)
|
||||
err = g.Websocket.Orderbook.Update(
|
||||
&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
CurrencyPair: currency.NewPairFromString(c),
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
@@ -337,8 +338,8 @@ func (g *Gateio) GenerateDefaultSubscriptions() {
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (g *Gateio) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
params := []interface{}{channelToSubscribe.Currency.String()}
|
||||
for _, paramValue := range channelToSubscribe.Params {
|
||||
params = append(params, paramValue)
|
||||
for i := range channelToSubscribe.Params {
|
||||
params = append(params, channelToSubscribe.Params[i])
|
||||
}
|
||||
|
||||
subscribe := WebsocketRequest{
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -102,6 +102,7 @@ func (g *Gateio) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user configuration
|
||||
@@ -138,6 +139,14 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
RateLimit: gateioWebsocketRateLimit,
|
||||
}
|
||||
|
||||
g.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -16,7 +17,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -113,9 +115,11 @@ func (g *Gemini) WsSecureSubscribe(dialer *websocket.Dialer, url string) error {
|
||||
headers.Add("Cache-Control", "no-cache")
|
||||
|
||||
g.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{
|
||||
ExchangeName: g.Name,
|
||||
URL: endpoint,
|
||||
Verbose: g.Verbose,
|
||||
ExchangeName: g.Name,
|
||||
URL: endpoint,
|
||||
Verbose: g.Verbose,
|
||||
ResponseCheckTimeout: responseCheckTimeout,
|
||||
ResponseMaxLimit: responseMaxLimit,
|
||||
}
|
||||
err = g.AuthenticatedWebsocketConn.Dial(dialer, headers)
|
||||
if err != nil {
|
||||
@@ -256,84 +260,73 @@ func (g *Gemini) WsHandleData() {
|
||||
func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pair) {
|
||||
if result.Timestamp == 0 && result.TimestampMS == 0 {
|
||||
var bids, asks []orderbook.Item
|
||||
for _, event := range result.Events {
|
||||
if event.Reason != "initial" {
|
||||
for i := range result.Events {
|
||||
if result.Events[i].Reason != "initial" {
|
||||
g.Websocket.DataHandler <- errors.New("gemini_websocket.go orderbook should be snapshot only")
|
||||
continue
|
||||
}
|
||||
|
||||
if event.Side == "ask" {
|
||||
if result.Events[i].Side == "ask" {
|
||||
asks = append(asks, orderbook.Item{
|
||||
Amount: event.Remaining,
|
||||
Price: event.Price,
|
||||
Amount: result.Events[i].Remaining,
|
||||
Price: result.Events[i].Price,
|
||||
})
|
||||
} else {
|
||||
bids = append(bids, orderbook.Item{
|
||||
Amount: event.Remaining,
|
||||
Price: event.Price,
|
||||
Amount: result.Events[i].Remaining,
|
||||
Price: result.Events[i].Price,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.Pair = pair
|
||||
|
||||
err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook,
|
||||
g.GetName(),
|
||||
false)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: g.GetName()}
|
||||
} else {
|
||||
for _, event := range result.Events {
|
||||
if event.Type == "trade" {
|
||||
var asks, bids []orderbook.Item
|
||||
for i := 0; i < len(result.Events); i++ {
|
||||
if result.Events[i].Type == "trade" {
|
||||
g.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Now(),
|
||||
CurrencyPair: pair,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: g.Name,
|
||||
EventTime: result.Timestamp,
|
||||
Price: event.Price,
|
||||
Amount: event.Amount,
|
||||
Side: event.MakerSide,
|
||||
Price: result.Events[i].Price,
|
||||
Amount: result.Events[i].Amount,
|
||||
Side: result.Events[i].MakerSide,
|
||||
}
|
||||
|
||||
} else {
|
||||
var i orderbook.Item
|
||||
i.Amount = event.Remaining
|
||||
i.Price = event.Price
|
||||
if event.Side == "ask" {
|
||||
err := g.Websocket.Orderbook.Update(nil,
|
||||
[]orderbook.Item{i},
|
||||
pair,
|
||||
time.Now(),
|
||||
g.GetName(),
|
||||
asset.Spot)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
item := orderbook.Item{
|
||||
Amount: result.Events[i].Remaining,
|
||||
Price: result.Events[i].Price,
|
||||
}
|
||||
if strings.EqualFold(result.Events[i].Side, exchange.AskOrderSide.ToString()) {
|
||||
asks = append(asks, item)
|
||||
} else {
|
||||
err := g.Websocket.Orderbook.Update([]orderbook.Item{i},
|
||||
nil,
|
||||
pair,
|
||||
time.Now(),
|
||||
g.GetName(),
|
||||
asset.Spot)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
bids = append(bids, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := g.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
CurrencyPair: pair,
|
||||
UpdateTime: time.Unix(0, result.TimestampMS),
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- fmt.Errorf("%v %v", g.Name, err)
|
||||
}
|
||||
g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: g.GetName()}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -97,6 +97,7 @@ func (g *Gemini) SetDefaults() {
|
||||
wshandler.WebsocketSequenceNumberSupported
|
||||
g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets exchange configuration parameters
|
||||
@@ -136,6 +137,14 @@ func (g *Gemini) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
g.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/nonce"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -227,13 +228,13 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
|
||||
}
|
||||
|
||||
var bids []orderbook.Item
|
||||
for _, bid := range ob.Params.Bid {
|
||||
bids = append(bids, orderbook.Item{Amount: bid.Size, Price: bid.Price})
|
||||
for i := range ob.Params.Bid {
|
||||
bids = append(bids, orderbook.Item{Amount: ob.Params.Bid[i].Size, Price: ob.Params.Bid[i].Price})
|
||||
}
|
||||
|
||||
var asks []orderbook.Item
|
||||
for _, ask := range ob.Params.Ask {
|
||||
asks = append(asks, orderbook.Item{Amount: ask.Size, Price: ask.Price})
|
||||
for i := range ob.Params.Ask {
|
||||
asks = append(asks, orderbook.Item{Amount: ob.Params.Ask[i].Size, Price: ob.Params.Ask[i].Price})
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(ob.Params.Symbol)
|
||||
@@ -242,10 +243,9 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.LastUpdated = time.Now()
|
||||
newOrderBook.Pair = p
|
||||
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false)
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -260,23 +260,28 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
|
||||
}
|
||||
|
||||
// WsProcessOrderbookUpdate updates a local cache
|
||||
func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error {
|
||||
if len(ob.Params.Bid) == 0 && len(ob.Params.Ask) == 0 {
|
||||
func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error {
|
||||
if len(update.Params.Bid) == 0 && len(update.Params.Ask) == 0 {
|
||||
return errors.New("hitbtc_websocket.go error - no data")
|
||||
}
|
||||
|
||||
var bids, asks []orderbook.Item
|
||||
for _, bid := range ob.Params.Bid {
|
||||
bids = append(bids, orderbook.Item{Price: bid.Price, Amount: bid.Size})
|
||||
for i := range update.Params.Bid {
|
||||
bids = append(bids, orderbook.Item{Price: update.Params.Bid[i].Price, Amount: update.Params.Bid[i].Size})
|
||||
}
|
||||
|
||||
for _, ask := range ob.Params.Ask {
|
||||
asks = append(asks, orderbook.Item{Price: ask.Price, Amount: ask.Size})
|
||||
for i := range update.Params.Ask {
|
||||
asks = append(asks, orderbook.Item{Price: update.Params.Ask[i].Price, Amount: update.Params.Ask[i].Size})
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(ob.Params.Symbol)
|
||||
|
||||
err := h.Websocket.Orderbook.Update(bids, asks, p, time.Now(), h.GetName(), asset.Spot)
|
||||
p := currency.NewPairFromString(update.Params.Symbol)
|
||||
err := h.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
CurrencyPair: p,
|
||||
UpdateID: update.Params.Sequence,
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -100,6 +100,7 @@ func (h *HitBTC) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user exchange configuration settings
|
||||
@@ -136,6 +137,14 @@ func (h *HitBTC) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
h.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// Please supply you own test keys here for due diligence testing.
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -245,7 +245,7 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
h.Websocket.DataHandler <- wshandler.KlineData{
|
||||
Timestamp: time.Unix(0, kline.Timestamp),
|
||||
Exchange: h.GetName(),
|
||||
AssetType: "SPOT",
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairFromString(data[1]),
|
||||
OpenPrice: kline.Tick.Open,
|
||||
ClosePrice: kline.Tick.Close,
|
||||
@@ -271,33 +271,27 @@ func (h *HUOBI) wsHandleMarketData(resp WsMessage) {
|
||||
}
|
||||
|
||||
// WsProcessOrderbook processes new orderbook data
|
||||
func (h *HUOBI) WsProcessOrderbook(ob *WsDepth, symbol string) error {
|
||||
var bids []orderbook.Item
|
||||
for _, data := range ob.Tick.Bids {
|
||||
bidLevel := data.([]interface{})
|
||||
func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error {
|
||||
p := currency.NewPairFromString(symbol)
|
||||
var bids, asks []orderbook.Item
|
||||
for i := 0; i < len(update.Tick.Bids); i++ {
|
||||
bidLevel := update.Tick.Bids[i].([]interface{})
|
||||
bids = append(bids, orderbook.Item{Price: bidLevel[0].(float64),
|
||||
Amount: bidLevel[0].(float64)})
|
||||
}
|
||||
|
||||
var asks []orderbook.Item
|
||||
for _, data := range ob.Tick.Asks {
|
||||
askLevel := data.([]interface{})
|
||||
for i := 0; i < len(update.Tick.Asks); i++ {
|
||||
askLevel := update.Tick.Asks[i].([]interface{})
|
||||
asks = append(asks, orderbook.Item{Price: askLevel[0].(float64),
|
||||
Amount: askLevel[0].(float64)})
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(symbol)
|
||||
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.Pair = p
|
||||
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false)
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Exchange: h.GetName(),
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -101,6 +101,7 @@ func (h *HUOBI) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user configuration
|
||||
@@ -149,6 +150,14 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
h.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// Please supply your own APIKEYS here for due diligence testing
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -272,33 +272,27 @@ func (h *HUOBIHADAX) wsHandleMarketData(resp WsMessage) {
|
||||
}
|
||||
|
||||
// WsProcessOrderbook processes new orderbook data
|
||||
func (h *HUOBIHADAX) WsProcessOrderbook(ob *WsDepth, symbol string) error {
|
||||
var bids []orderbook.Item
|
||||
for _, data := range ob.Tick.Bids {
|
||||
bidLevel := data.([]interface{})
|
||||
func (h *HUOBIHADAX) WsProcessOrderbook(update *WsDepth, symbol string) error {
|
||||
p := currency.NewPairFromString(symbol)
|
||||
var bids, asks []orderbook.Item
|
||||
for i := 0; i < len(update.Tick.Bids); i++ {
|
||||
bidLevel := update.Tick.Bids[i].([]interface{})
|
||||
bids = append(bids, orderbook.Item{Price: bidLevel[0].(float64),
|
||||
Amount: bidLevel[0].(float64)})
|
||||
}
|
||||
|
||||
var asks []orderbook.Item
|
||||
for _, data := range ob.Tick.Asks {
|
||||
askLevel := data.([]interface{})
|
||||
for i := 0; i < len(update.Tick.Asks); i++ {
|
||||
askLevel := update.Tick.Asks[i].([]interface{})
|
||||
asks = append(asks, orderbook.Item{Price: askLevel[0].(float64),
|
||||
Amount: askLevel[0].(float64)})
|
||||
}
|
||||
|
||||
p := currency.NewPairFromString(symbol)
|
||||
|
||||
var newOrderBook orderbook.Base
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.Pair = p
|
||||
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, h.GetName(), false)
|
||||
err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Exchange: h.GetName(),
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -100,6 +100,7 @@ func (h *HUOBIHADAX) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user configuration
|
||||
@@ -145,6 +146,14 @@ func (h *HUOBIHADAX) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
h.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// IBotExchange enforces standard functions for all exchanges supported in
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
package kraken
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
var k Kraken
|
||||
@@ -657,107 +654,6 @@ func TestWithdrawCancel(t *testing.T) {
|
||||
|
||||
// ---------------------------- Websocket tests -----------------------------------------
|
||||
|
||||
// TestOrderbookBufferReset websocket test
|
||||
func TestOrderbookBufferReset(t *testing.T) {
|
||||
if k.Name == "" {
|
||||
k.SetDefaults()
|
||||
TestSetup(t)
|
||||
}
|
||||
if !k.Websocket.IsEnabled() {
|
||||
t.Skip("Websocket not enabled, skipping")
|
||||
}
|
||||
var obUpdates []string
|
||||
obpartial := `[0,{"as":[["5541.30000","2.50700000","0"]],"bs":[["5541.20000","1.52900000","0"]]}]`
|
||||
for i := 1; i < orderbookBufferLimit+2; i++ {
|
||||
obUpdates = append(obUpdates, fmt.Sprintf(`[0,{"a":[["5541.30000","2.50700000","%v"]],"b":[["5541.30000","1.00000000","%v"]]}]`, i, i))
|
||||
}
|
||||
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
var dataResponse WebsocketDataResponse
|
||||
err := common.JSONDecode([]byte(obpartial), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData := dataResponse[1].(map[string]interface{})
|
||||
channelData := WebsocketChannelData{
|
||||
ChannelID: 0,
|
||||
Subscription: "orderbook",
|
||||
Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"),
|
||||
}
|
||||
|
||||
k.wsProcessOrderBookPartial(
|
||||
&channelData,
|
||||
obData,
|
||||
)
|
||||
|
||||
for i := 0; i < len(obUpdates); i++ {
|
||||
err = common.JSONDecode([]byte(obUpdates[i]), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData = dataResponse[1].(map[string]interface{})
|
||||
if i < len(obUpdates)-1 {
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
} else if i == len(obUpdates)-1 {
|
||||
k.wsProcessOrderBookUpdate(&channelData)
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
if len(orderbookBuffer[channelData.ChannelID]) != 1 {
|
||||
t.Error("Buffer should have 1 entry after being reset")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrderbookBufferReset websocket test
|
||||
func TestOrderBookOutOfOrder(t *testing.T) {
|
||||
if k.Name == "" {
|
||||
k.SetDefaults()
|
||||
TestSetup(t)
|
||||
}
|
||||
if !k.Websocket.IsEnabled() {
|
||||
t.Skip("Websocket not enabled, skipping")
|
||||
}
|
||||
obpartial := `[0,{"as":[["5541.30000","2.50700000","0"]],"bs":[["5541.20000","1.52900000","5"]]}]`
|
||||
obupdate1 := `[0,{"a":[["5541.30000","0.00000000","1"]],"b":[["5541.30000","0.00000000","3"]]}]`
|
||||
obupdate2 := `[0,{"a":[["5541.30000","2.50700000","2"]],"b":[["5541.30000","0.00000000","1"]]}]`
|
||||
|
||||
k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
var dataResponse WebsocketDataResponse
|
||||
err := common.JSONDecode([]byte(obpartial), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData := dataResponse[1].(map[string]interface{})
|
||||
channelData := WebsocketChannelData{
|
||||
ChannelID: 0,
|
||||
Subscription: "orderbook",
|
||||
Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"),
|
||||
}
|
||||
|
||||
k.wsProcessOrderBookPartial(
|
||||
&channelData,
|
||||
obData,
|
||||
)
|
||||
|
||||
err = common.JSONDecode([]byte(obupdate1), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData = dataResponse[1].(map[string]interface{})
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
|
||||
err = common.JSONDecode([]byte(obupdate2), &dataResponse)
|
||||
if err != nil {
|
||||
t.Errorf("Could not parse, %v", err)
|
||||
}
|
||||
obData = dataResponse[1].(map[string]interface{})
|
||||
k.wsProcessOrderBookBuffer(&channelData, obData)
|
||||
|
||||
err = k.wsProcessOrderBookUpdate(&channelData)
|
||||
if !strings.Contains(err.Error(), "orderbook update out of order") {
|
||||
t.Error("Expected out of order orderbook error")
|
||||
}
|
||||
}
|
||||
|
||||
func setupWsTests(t *testing.T) {
|
||||
if wsSetupRan {
|
||||
return
|
||||
|
||||
@@ -5,9 +5,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -15,7 +13,8 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -40,22 +39,13 @@ const (
|
||||
krakenWsTrade = "trade"
|
||||
krakenWsSpread = "spread"
|
||||
krakenWsOrderbook = "book"
|
||||
// Only supported asset type
|
||||
|
||||
orderbookBufferLimit = 3
|
||||
krakenWsRateLimit = 50
|
||||
)
|
||||
|
||||
// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time
|
||||
var orderbookMutex sync.Mutex
|
||||
var subscriptionChannelPair []WebsocketChannelData
|
||||
|
||||
// krakenOrderBooks TODO THIS IS A TEMPORARY SOLUTION UNTIL ENGINE BRANCH IS MERGED
|
||||
// WS orderbook data can only rely on WS orderbook data
|
||||
// Currently REST and WS runs simultaneously, dirtying the data
|
||||
var krakenOrderBooks map[int64]orderbook.Base
|
||||
|
||||
// orderbookBuffer Stores orderbook updates per channel
|
||||
var orderbookBuffer map[int64][]orderbook.Base
|
||||
var subscribeToDefaultChannels = true
|
||||
|
||||
// Channels require a topic and a currency
|
||||
@@ -229,22 +219,6 @@ func (k *Kraken) WsHandleEventResponse(response *WebsocketEventResponse, rawResp
|
||||
// addNewSubscriptionChannelData stores channel ids, pairs and subscription types to an array
|
||||
// allowing correlation between subscriptions and returned data
|
||||
func addNewSubscriptionChannelData(response *WebsocketEventResponse) {
|
||||
for i := range subscriptionChannelPair {
|
||||
if response.ChannelID != subscriptionChannelPair[i].ChannelID {
|
||||
continue
|
||||
}
|
||||
// kill the stale orderbooks due to resubscribing
|
||||
if orderbookBuffer == nil {
|
||||
orderbookBuffer = make(map[int64][]orderbook.Base)
|
||||
}
|
||||
orderbookBuffer[response.ChannelID] = []orderbook.Base{}
|
||||
if krakenOrderBooks == nil {
|
||||
krakenOrderBooks = make(map[int64]orderbook.Base)
|
||||
}
|
||||
krakenOrderBooks[response.ChannelID] = orderbook.Base{}
|
||||
return
|
||||
}
|
||||
|
||||
// We change the / to - to maintain compatibility with REST/config
|
||||
pair := currency.NewPairWithDelimiter(response.Pair.Base.String(), response.Pair.Quote.String(), "-")
|
||||
subscriptionChannelPair = append(subscriptionChannelPair, WebsocketChannelData{
|
||||
@@ -349,16 +323,13 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
|
||||
if asksExist || bidsExist {
|
||||
k.wsRequestMtx.Lock()
|
||||
defer k.wsRequestMtx.Unlock()
|
||||
k.wsProcessOrderBookBuffer(channelData, obData)
|
||||
if len(orderbookBuffer[channelData.ChannelID]) >= orderbookBufferLimit {
|
||||
err := k.wsProcessOrderBookUpdate(channelData)
|
||||
if err != nil {
|
||||
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
|
||||
Channel: krakenWsOrderbook,
|
||||
Currency: channelData.Pair,
|
||||
}
|
||||
k.Websocket.ResubscribeToChannel(subscriptionToRemove)
|
||||
err := k.wsProcessOrderBookUpdate(channelData, obData)
|
||||
if err != nil {
|
||||
subscriptionToRemove := wshandler.WebsocketChannelSubscription{
|
||||
Channel: krakenWsOrderbook,
|
||||
Currency: channelData.Pair,
|
||||
}
|
||||
k.Websocket.ResubscribeToChannel(subscriptionToRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,7 +337,7 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data inte
|
||||
|
||||
// wsProcessOrderBookPartial creates a new orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, obData map[string]interface{}) {
|
||||
ob := orderbook.Base{
|
||||
base := orderbook.Base{
|
||||
Pair: channelData.Pair,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
@@ -378,11 +349,10 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
asks := askData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(asks[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
|
||||
ob.Asks = append(ob.Asks, orderbook.Item{
|
||||
base.Asks = append(base.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
|
||||
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -390,17 +360,15 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
highestLastUpdate = askUpdatedTime
|
||||
}
|
||||
}
|
||||
|
||||
bidData := obData["bs"].([]interface{})
|
||||
for i := range bidData {
|
||||
bids := bidData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(bids[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
|
||||
ob.Bids = append(ob.Bids, orderbook.Item{
|
||||
base.Bids = append(base.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
|
||||
timeData, _ := strconv.ParseFloat(bids[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
bidUpdateTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -408,33 +376,25 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, ob
|
||||
highestLastUpdate = bidUpdateTime
|
||||
}
|
||||
}
|
||||
|
||||
ob.LastUpdated = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&ob, k.Name, true)
|
||||
base.LastUpdated = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&base, true)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: k.Name,
|
||||
Asset: asset.Spot,
|
||||
Pair: channelData.Pair,
|
||||
}
|
||||
|
||||
if krakenOrderBooks == nil {
|
||||
krakenOrderBooks = make(map[int64]orderbook.Base)
|
||||
}
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
}
|
||||
|
||||
func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obData map[string]interface{}) {
|
||||
ob := orderbook.Base{
|
||||
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, obData map[string]interface{}) error {
|
||||
update := wsorderbook.WebsocketOrderbookUpdate{
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: k.Name,
|
||||
Pair: channelData.Pair,
|
||||
CurrencyPair: channelData.Pair,
|
||||
}
|
||||
|
||||
var highestLastUpdate time.Time
|
||||
// Ask data is not always sent
|
||||
if _, ok := obData["a"]; ok {
|
||||
@@ -443,11 +403,10 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
|
||||
asks := askData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(asks[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(asks[1].(string), 64)
|
||||
ob.Asks = append(ob.Asks, orderbook.Item{
|
||||
update.Asks = append(update.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
|
||||
timeData, _ := strconv.ParseFloat(asks[2].(string), 64)
|
||||
sec, dec := math.Modf(timeData)
|
||||
askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9)))
|
||||
@@ -463,7 +422,7 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
|
||||
bids := bidData[i].([]interface{})
|
||||
price, _ := strconv.ParseFloat(bids[0].(string), 64)
|
||||
amount, _ := strconv.ParseFloat(bids[1].(string), 64)
|
||||
ob.Bids = append(ob.Bids, orderbook.Item{
|
||||
update.Bids = append(update.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
@@ -475,201 +434,20 @@ func (k *Kraken) wsProcessOrderBookBuffer(channelData *WebsocketChannelData, obD
|
||||
}
|
||||
}
|
||||
}
|
||||
ob.LastUpdated = highestLastUpdate
|
||||
if orderbookBuffer == nil {
|
||||
orderbookBuffer = make(map[int64][]orderbook.Base)
|
||||
}
|
||||
orderbookBuffer[channelData.ChannelID] = append(orderbookBuffer[channelData.ChannelID], ob)
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Adding orderbook to buffer for channel %v. Lastupdated: %v. %v / %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
ob.LastUpdated,
|
||||
len(orderbookBuffer[channelData.ChannelID]),
|
||||
orderbookBufferLimit)
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair
|
||||
func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData) error {
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Current orderbook 'LastUpdated': %v",
|
||||
k.Name,
|
||||
krakenOrderBooks[channelData.ChannelID].LastUpdated)
|
||||
}
|
||||
lowestLastUpdated := orderbookBuffer[channelData.ChannelID][0].LastUpdated
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Sorting orderbook. Earliest 'LastUpdated' entry: %v",
|
||||
k.Name,
|
||||
lowestLastUpdated)
|
||||
}
|
||||
sort.Slice(orderbookBuffer[channelData.ChannelID], func(i, j int) bool {
|
||||
return orderbookBuffer[channelData.ChannelID][i].LastUpdated.Before(orderbookBuffer[channelData.ChannelID][j].LastUpdated)
|
||||
})
|
||||
|
||||
lowestLastUpdated = orderbookBuffer[channelData.ChannelID][0].LastUpdated
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Sorted orderbook. Earliest 'LastUpdated' entry: %v",
|
||||
k.Name,
|
||||
lowestLastUpdated)
|
||||
}
|
||||
// The earliest update has to be after the previously stored orderbook
|
||||
if krakenOrderBooks[channelData.ChannelID].LastUpdated.After(lowestLastUpdated) {
|
||||
err := fmt.Errorf("%v orderbook update out of order. Existing: %v, Attempted: %v",
|
||||
k.Name,
|
||||
krakenOrderBooks[channelData.ChannelID].LastUpdated,
|
||||
lowestLastUpdated)
|
||||
k.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
|
||||
k.updateChannelOrderbookEntries(channelData)
|
||||
highestLastUpdate := orderbookBuffer[channelData.ChannelID][len(orderbookBuffer[channelData.ChannelID])-1].LastUpdated
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Saving orderbook. Lastupdated: %v",
|
||||
k.Name,
|
||||
highestLastUpdate)
|
||||
}
|
||||
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.LastUpdated = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.LoadSnapshot(&ob, k.Name, true)
|
||||
update.UpdateTime = highestLastUpdate
|
||||
err := k.Websocket.Orderbook.Update(&update)
|
||||
if err != nil {
|
||||
k.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
|
||||
k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: k.Name,
|
||||
Asset: asset.Spot,
|
||||
Pair: channelData.Pair,
|
||||
}
|
||||
// Reset the buffer
|
||||
orderbookBuffer[channelData.ChannelID] = []orderbook.Base{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookEntries(channelData *WebsocketChannelData) {
|
||||
for i := 0; i < len(orderbookBuffer[channelData.ChannelID]); i++ {
|
||||
for j := 0; j < len(orderbookBuffer[channelData.ChannelID][i].Asks); j++ {
|
||||
k.updateChannelOrderbookAsks(i, j, channelData)
|
||||
}
|
||||
for j := 0; j < len(orderbookBuffer[channelData.ChannelID][i].Bids); j++ {
|
||||
k.updateChannelOrderbookBids(i, j, channelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookAsks(i, j int, channelData *WebsocketChannelData) {
|
||||
askFound := k.updateChannelOrderbookAsk(i, j, channelData)
|
||||
if !askFound {
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Adding Ask for channel %v. Price %v. Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Asks = append(ob.Asks, orderbookBuffer[channelData.ChannelID][i].Asks[j])
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookAsk(i, j int, channelData *WebsocketChannelData) bool {
|
||||
askFound := false
|
||||
for l := 0; l < len(krakenOrderBooks[channelData.ChannelID].Asks); l++ {
|
||||
if krakenOrderBooks[channelData.ChannelID].Asks[l].Price == orderbookBuffer[channelData.ChannelID][i].Asks[j].Price {
|
||||
askFound = true
|
||||
if orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount == 0 {
|
||||
// Remove existing entry
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Removing Ask for channel %v. Price %v. Old amount %v. Buffer %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount, i)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Asks = append(ob.Asks[:l], ob.Asks[l+1:]...)
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
l--
|
||||
} else if krakenOrderBooks[channelData.ChannelID].Asks[l].Amount != orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount {
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%v Updating Ask for channel %v. Price %v. Old amount %v, New Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount,
|
||||
orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount)
|
||||
}
|
||||
krakenOrderBooks[channelData.ChannelID].Asks[l].Amount = orderbookBuffer[channelData.ChannelID][i].Asks[j].Amount
|
||||
}
|
||||
return askFound
|
||||
}
|
||||
}
|
||||
return askFound
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookBids(i, j int, channelData *WebsocketChannelData) {
|
||||
bidFound := k.updateChannelOrderbookBid(i, j, channelData)
|
||||
if !bidFound {
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Adding Bid for channel %v. Price %v. Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Bids = append(ob.Bids, orderbookBuffer[channelData.ChannelID][i].Bids[j])
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kraken) updateChannelOrderbookBid(i, j int, channelData *WebsocketChannelData) bool {
|
||||
bidFound := false
|
||||
for l := 0; l < len(krakenOrderBooks[channelData.ChannelID].Bids); l++ {
|
||||
if krakenOrderBooks[channelData.ChannelID].Bids[l].Price == orderbookBuffer[channelData.ChannelID][i].Bids[j].Price {
|
||||
bidFound = true
|
||||
if orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount == 0 {
|
||||
// Remove existing entry
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Removing Bid for channel %v. Price %v. Old amount %v. Buffer %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount, i)
|
||||
}
|
||||
ob := krakenOrderBooks[channelData.ChannelID]
|
||||
ob.Bids = append(ob.Bids[:l], ob.Bids[l+1:]...)
|
||||
krakenOrderBooks[channelData.ChannelID] = ob
|
||||
l--
|
||||
} else if krakenOrderBooks[channelData.ChannelID].Bids[l].Amount != orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount {
|
||||
if k.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Updating Bid for channel %v. Price %v. Old amount %v, New Amount %v",
|
||||
k.Name,
|
||||
channelData.ChannelID,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Price,
|
||||
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount,
|
||||
orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount)
|
||||
}
|
||||
krakenOrderBooks[channelData.ChannelID].Bids[l].Amount = orderbookBuffer[channelData.ChannelID][i].Bids[j].Amount
|
||||
}
|
||||
return bidFound
|
||||
}
|
||||
}
|
||||
return bidFound
|
||||
}
|
||||
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data interface{}) {
|
||||
candleData := data.([]interface{})
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -103,6 +103,7 @@ func (k *Kraken) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
k.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
k.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
k.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets current exchange configuration
|
||||
@@ -139,6 +140,14 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
k.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
### Current Features
|
||||
|
||||
+ REST Support
|
||||
+ Websocket Support
|
||||
|
||||
### How to enable
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
// LakeBTC is the overarching type across the LakeBTC package
|
||||
type LakeBTC struct {
|
||||
exchange.Base
|
||||
WebsocketConn
|
||||
}
|
||||
|
||||
// GetTicker returns the current ticker from lakeBTC
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package lakebtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
@@ -8,9 +9,12 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
var l LakeBTC
|
||||
var setupRan bool
|
||||
|
||||
// Please add your own APIkeys to do correct due diligence testing.
|
||||
const (
|
||||
@@ -20,21 +24,27 @@ const (
|
||||
)
|
||||
|
||||
func TestSetDefaults(t *testing.T) {
|
||||
l.SetDefaults()
|
||||
if !setupRan {
|
||||
l.SetDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig("../../testdata/configtest.json")
|
||||
lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC")
|
||||
if err != nil {
|
||||
t.Error("Test Failed - LakeBTC Setup() init error")
|
||||
if !setupRan {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig("../../testdata/configtest.json")
|
||||
lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC")
|
||||
if err != nil {
|
||||
t.Error("Test Failed - LakeBTC Setup() init error")
|
||||
}
|
||||
lakebtcConfig.API.AuthenticatedSupport = true
|
||||
lakebtcConfig.API.Credentials.Key = apiKey
|
||||
lakebtcConfig.API.Credentials.Secret = apiSecret
|
||||
lakebtcConfig.Features.Enabled.Websocket = true
|
||||
l.Setup(lakebtcConfig)
|
||||
l.API.Endpoints.WebsocketURL = lakeBTCWSURL
|
||||
setupRan = true
|
||||
}
|
||||
lakebtcConfig.API.AuthenticatedSupport = true
|
||||
lakebtcConfig.API.Credentials.Key = apiKey
|
||||
lakebtcConfig.API.Credentials.Secret = apiSecret
|
||||
|
||||
l.Setup(lakebtcConfig)
|
||||
}
|
||||
|
||||
func TestFetchTradablePairs(t *testing.T) {
|
||||
@@ -448,3 +458,65 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsConn websocket connection test
|
||||
func TestWsConn(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
TestSetup(t)
|
||||
if !l.Websocket.IsEnabled() {
|
||||
t.Skip(wshandler.WebsocketNotEnabled)
|
||||
}
|
||||
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
err := l.WsConnect()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsTradeProcessing logic test
|
||||
func TestWsTradeProcessing(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
TestSetup(t)
|
||||
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
json := `{"trades":[{"type":"sell","date":1564985787,"price":"11913.02","amount":"0.49"}]}`
|
||||
err := l.processTrades(json, "market-btcusd-global")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsTickerProcessing logic test
|
||||
func TestWsTickerProcessing(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
TestSetup(t)
|
||||
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
json := `{"btcusd":{"low":"10990.05","high":"11966.24","last":"11903.29","volume":"1803.967079","sell":"11912.39","buy":"11902.2"},"btceur":{"low":"9886.87","high":"10732.72","last":"10691.44","volume":"87.994478","sell":"10711.62","buy":"10691.44"},"btchkd":{"low":null,"high":null,"last":"51776.98","volume":null,"sell":"93307.37","buy":"93177.56"},"btcjpy":{"low":"1176039.0","high":"1272246.0","last":"1265680.0","volume":"129.021421","sell":"1266764.0","buy":"1265680.0"},"btcgbp":{"low":"9157.12","high":"9953.43","last":"9941.28","volume":"10.4997","sell":"10007.89","buy":"9941.28"},"btcaud":{"low":"16102.57","high":"17594.22","last":"17548.16","volume":"7.338316","sell":"17616.67","buy":"17549.69"},"btccad":{"low":"14541.69","high":"15834.87","last":"15763.54","volume":"30.480309","sell":"15793.45","buy":"15756.13"},"btcsgd":{"low":"15133.82","high":"16501.62","last":"16455.53","volume":"4.044026","sell":"16484.37","buy":"16462.18"},"btcchf":{"low":"10800.58","high":"11526.24","last":"11526.24","volume":"0.1765","sell":"11675.34","buy":"11632.02"},"btcnzd":{"low":null,"high":null,"last":"8340.98","volume":null,"sell":"18315.49","buy":"18221.37"},"btcngn":{"low":null,"high":null,"last":"600000.0","volume":null,"sell":null,"buy":null},"eurusd":{"low":"1.1088","high":"1.1138","last":"1.1125","volume":"2680.105249","sell":"1.1142","buy":"1.1121"},"gbpusd":{"low":"1.1934","high":"1.1958","last":"1.1934","volume":"1493.923823","sell":"1.1979","buy":"1.1903"},"usdjpy":{"low":"105.26","high":"107.25","last":"106.33","volume":"114490.2179","sell":"106.34","buy":"106.27"},"usdhkd":{"low":null,"high":null,"last":"7.851","volume":null,"sell":"7.8328","buy":"7.8286"},"usdcad":{"low":"1.3225","high":"1.3272","last":"1.3255","volume":"11033.9877","sell":"1.3258","buy":"1.3238"},"usdsgd":{"low":"1.3776","high":"1.3839","last":"1.3838","volume":"2523.75","sell":"1.3838","buy":"1.3819"},"audusd":{"low":"0.6764","high":"0.6853","last":"0.6771","volume":"5442.608321","sell":"0.6782","buy":"0.6762"},"nzdusd":{"low":null,"high":null,"last":"0.6758","volume":null,"sell":"0.6532","buy":"0.6504"},"usdchf":{"low":"0.9838","high":"0.9838","last":"0.9838","volume":"108.3352","sell":"0.9801","buy":"0.9773"},"usdngn":{"low":null,"high":null,"last":"200.0","volume":null,"sell":null,"buy":null},"ethbtc":{"low":"0.0205","high":"0.025","last":"0.0205","volume":null,"sell":"0.03","buy":"0.0194"},"ltcbtc":{"low":null,"high":null,"last":"0.0114","volume":null,"sell":"0.009","buy":"0.0073"},"bchbtc":{"low":null,"high":null,"last":"0.0544","volume":null,"sell":"0.0322","buy":"0.0274"},"xrpbtc":{"low":"0.000042","high":"0.000042","last":"0.000042","volume":null,"sell":"0.000037","buy":"0.000022"},"baceth":{"low":"0.000035","high":"0.000035","last":"0.000035","volume":null,"sell":"0.0015","buy":null}}`
|
||||
err := l.processTicker(json)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrencyFromChannel(t *testing.T) {
|
||||
curr := currency.NewPair(currency.LTC, currency.BTC)
|
||||
result := l.getCurrencyFromChannel(fmt.Sprintf("%v%v%v", marketSubstring, curr, globalSubstring))
|
||||
if curr != result {
|
||||
t.Errorf("currency result is not equal. Expected %v", curr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsOrderbookProcessing logic test
|
||||
func TestWsOrderbookProcessing(t *testing.T) {
|
||||
TestSetDefaults(t)
|
||||
TestSetup(t)
|
||||
l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
|
||||
l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
|
||||
json := `{"asks":[["11905.66","0.0019"],["11905.73","0.0015"],["11906.43","0.0013"],["11906.62","0.0019"],["11907.25","11.087"],["11907.66","0.0006"],["11907.73","0.3113"],["11907.84","0.0006"],["11908.37","0.0016"],["11908.86","10.3786"],["11909.54","4.2955"],["11910.15","0.0012"],["11910.56","13.5505"],["11911.06","0.0011"],["11911.37","0.0023"]],"bids":[["11905.55","0.0171"],["11904.43","0.0225"],["11903.31","0.0223"],["11902.2","0.0027"],["11901.92","1.002"],["11901.6","0.0015"],["11901.49","0.0012"],["11901.08","0.0227"],["11900.93","0.0009"],["11900.53","1.662"],["11900.08","0.001"],["11900.01","3.6745"],["11899.96","0.003"],["11899.91","0.0006"],["11899.44","0.0013"]]}`
|
||||
err := l.processOrderbook(json, "market-btcusd-global")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
252
exchanges/lakebtc/lakebtc_websocket.go
Normal file
252
exchanges/lakebtc/lakebtc_websocket.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package lakebtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
"github.com/toorop/go-pusher"
|
||||
)
|
||||
|
||||
const (
|
||||
lakeBTCWSURL = "ws.lakebtc.com:8085"
|
||||
marketGlobalEndpoint = "market-global"
|
||||
marketSubstring = "market-"
|
||||
globalSubstring = "-global"
|
||||
volumeString = "volume"
|
||||
highString = "high"
|
||||
lowString = "low"
|
||||
wssSchem = "wss"
|
||||
)
|
||||
|
||||
// WsConnect initiates a new websocket connection
|
||||
func (l *LakeBTC) WsConnect() error {
|
||||
if !l.Websocket.IsEnabled() || !l.IsEnabled() {
|
||||
return errors.New(wshandler.WebsocketNotEnabled)
|
||||
}
|
||||
var err error
|
||||
l.WebsocketConn.Client, err = pusher.NewCustomClient(strings.ToLower(l.Name), lakeBTCWSURL, wssSchem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = l.WebsocketConn.Client.Subscribe(marketGlobalEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.GenerateDefaultSubscriptions()
|
||||
err = l.listenToEndpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go l.wsHandleIncomingData()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LakeBTC) listenToEndpoints() error {
|
||||
var err error
|
||||
l.WebsocketConn.Ticker, err = l.WebsocketConn.Client.Bind("tickers")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err)
|
||||
}
|
||||
l.WebsocketConn.Orderbook, err = l.WebsocketConn.Client.Bind("update")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err)
|
||||
}
|
||||
l.WebsocketConn.Trade, err = l.WebsocketConn.Client.Bind("trades")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s Websocket Bind error: %s", l.GetName(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (l *LakeBTC) GenerateDefaultSubscriptions() {
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
enabledCurrencies := l.GetEnabledPairs(asset.Spot)
|
||||
for j := range enabledCurrencies {
|
||||
enabledCurrencies[j].Delimiter = ""
|
||||
channel := fmt.Sprintf("%v%v%v", marketSubstring, enabledCurrencies[j].Lower(), globalSubstring)
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: channel,
|
||||
Currency: enabledCurrencies[j],
|
||||
})
|
||||
}
|
||||
l.Websocket.SubscribeToChannels(subscriptions)
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (l *LakeBTC) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
return l.WebsocketConn.Client.Subscribe(channelToSubscribe.Channel)
|
||||
}
|
||||
|
||||
// wsHandleIncomingData services incoming data from the websocket connection
|
||||
func (l *LakeBTC) wsHandleIncomingData() {
|
||||
l.Websocket.Wg.Add(1)
|
||||
defer l.Websocket.Wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-l.Websocket.ShutdownC:
|
||||
return
|
||||
case data := <-l.WebsocketConn.Ticker:
|
||||
if l.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Websocket message received: %v", l.Name, data)
|
||||
}
|
||||
l.Websocket.TrafficAlert <- struct{}{}
|
||||
err := l.processTicker(data.Data)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
case data := <-l.WebsocketConn.Trade:
|
||||
l.Websocket.TrafficAlert <- struct{}{}
|
||||
if l.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Websocket message received: %v", l.Name, data)
|
||||
}
|
||||
err := l.processTrades(data.Data, data.Channel)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
case data := <-l.WebsocketConn.Orderbook:
|
||||
l.Websocket.TrafficAlert <- struct{}{}
|
||||
if l.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%v Websocket message received: %v", l.Name, data)
|
||||
}
|
||||
err := l.processOrderbook(data.Data, data.Channel)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LakeBTC) processTrades(data, channel string) error {
|
||||
var tradeData WsTrades
|
||||
err := common.JSONDecode([]byte(data), &tradeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
curr := l.getCurrencyFromChannel(channel)
|
||||
for i := 0; i < len(tradeData.Trades); i++ {
|
||||
l.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(tradeData.Trades[i].Date, 0),
|
||||
CurrencyPair: curr,
|
||||
AssetType: asset.Spot,
|
||||
Exchange: l.GetName(),
|
||||
EventType: asset.Spot.String(),
|
||||
EventTime: tradeData.Trades[i].Date,
|
||||
Price: tradeData.Trades[i].Price,
|
||||
Amount: tradeData.Trades[i].Amount,
|
||||
Side: tradeData.Trades[i].Type,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LakeBTC) processOrderbook(obUpdate, channel string) error {
|
||||
var update WsOrderbookUpdate
|
||||
err := common.JSONDecode([]byte(obUpdate), &update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
book := orderbook.Base{
|
||||
Pair: l.getCurrencyFromChannel(channel),
|
||||
LastUpdated: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
ExchangeName: l.Name,
|
||||
}
|
||||
|
||||
for i := 0; i < len(update.Asks); i++ {
|
||||
var amount, price float64
|
||||
amount, err = strconv.ParseFloat(update.Asks[i][1], 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, update.Asks[i])
|
||||
continue
|
||||
}
|
||||
price, err = strconv.ParseFloat(update.Asks[i][0], 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing orderbook price %v", l.Name, update.Asks[i])
|
||||
continue
|
||||
}
|
||||
book.Asks = append(book.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
for i := 0; i < len(update.Bids); i++ {
|
||||
var amount, price float64
|
||||
amount, err = strconv.ParseFloat(update.Bids[i][1], 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, update.Bids[i])
|
||||
continue
|
||||
}
|
||||
price, err = strconv.ParseFloat(update.Bids[i][0], 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing orderbook price %v", l.Name, update.Bids[i])
|
||||
continue
|
||||
}
|
||||
book.Bids = append(book.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
return l.Websocket.Orderbook.LoadSnapshot(&book, true)
|
||||
}
|
||||
|
||||
func (l *LakeBTC) getCurrencyFromChannel(channel string) currency.Pair {
|
||||
curr := strings.Replace(channel, marketSubstring, "", 1)
|
||||
curr = strings.Replace(curr, globalSubstring, "", 1)
|
||||
return currency.NewPairFromString(curr)
|
||||
}
|
||||
|
||||
func (l *LakeBTC) processTicker(ticker string) error {
|
||||
var tUpdate map[string]interface{}
|
||||
err := common.JSONDecode([]byte(ticker), &tUpdate)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- err
|
||||
return err
|
||||
}
|
||||
for k, v := range tUpdate {
|
||||
tickerData := v.(map[string]interface{})
|
||||
if tickerData[highString] == nil || tickerData[lowString] == nil || tickerData[volumeString] == nil {
|
||||
continue
|
||||
}
|
||||
high, err := strconv.ParseFloat(tickerData[highString].(string), 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'high' %v", l.Name, tickerData)
|
||||
continue
|
||||
}
|
||||
low, err := strconv.ParseFloat(tickerData[lowString].(string), 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, tickerData)
|
||||
continue
|
||||
}
|
||||
vol, err := strconv.ParseFloat(tickerData[volumeString].(string), 64)
|
||||
if err != nil {
|
||||
l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'volume' %v", l.Name, tickerData)
|
||||
continue
|
||||
}
|
||||
l.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Timestamp: time.Now(),
|
||||
Pair: currency.NewPairFromString(k),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: l.GetName(),
|
||||
Quantity: vol,
|
||||
HighPrice: high,
|
||||
LowPrice: low,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ func (l *LakeBTC) SetDefaults() {
|
||||
l.Features = exchange.Features{
|
||||
Supports: exchange.FeaturesSupported{
|
||||
REST: true,
|
||||
Websocket: false,
|
||||
Websocket: true,
|
||||
RESTCapabilities: exchange.ProtocolFeatures{
|
||||
AutoPairUpdates: true,
|
||||
TickerBatching: true,
|
||||
@@ -88,6 +88,14 @@ func (l *LakeBTC) SetDefaults() {
|
||||
|
||||
l.API.Endpoints.URLDefault = lakeBTCAPIURL
|
||||
l.API.Endpoints.URL = l.API.Endpoints.URLDefault
|
||||
l.Websocket = wshandler.New()
|
||||
l.API.Endpoints.WebsocketURL = lakeBTCWSURL
|
||||
l.Websocket.Functionality = wshandler.WebsocketOrderbookSupported |
|
||||
wshandler.WebsocketTradeDataSupported |
|
||||
wshandler.WebsocketSubscribeSupported
|
||||
l.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
l.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
l.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets exchange configuration profile
|
||||
@@ -97,7 +105,32 @@ func (l *LakeBTC) Setup(exch *config.ExchangeConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l.SetupDefaults(exch)
|
||||
err := l.SetupDefaults(exch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = l.Websocket.Setup(l.WsConnect,
|
||||
l.Subscribe,
|
||||
nil,
|
||||
exch.Name,
|
||||
exch.Features.Enabled.Websocket,
|
||||
exch.Verbose,
|
||||
lakeBTCWSURL,
|
||||
exch.API.Endpoints.WebsocketURL,
|
||||
exch.API.AuthenticatedWebsocketSupport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the LakeBTC go routine
|
||||
@@ -373,8 +406,7 @@ func (l *LakeBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *exchange
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (l *LakeBTC) GetWebsocket() (*wshandler.Websocket, error) {
|
||||
// Documents are too vague to implement
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
return l.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/okgroup"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
// Please supply you own test keys here for due diligence testing.
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -104,6 +104,7 @@ func (o *OKCoin) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Start starts the OKGroup go routine
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -104,6 +104,7 @@ func (o *OKEX) SetDefaults() {
|
||||
wshandler.WebsocketMessageCorrelationSupported
|
||||
o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Start starts the OKGroup go routine
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -18,7 +17,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -311,7 +311,7 @@ func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string {
|
||||
// eg "spot/ticker:BTCUSD" results in "SPOT"
|
||||
func (o *OKGroup) GetAssetTypeFromTableName(table string) asset.Item {
|
||||
assetIndex := strings.Index(table, "/")
|
||||
return asset.Item(strings.ToUpper(table[:assetIndex]))
|
||||
return asset.Item(table[:assetIndex])
|
||||
}
|
||||
|
||||
// WsHandleDataResponse classifies the WS response and sends to appropriate handler
|
||||
@@ -469,7 +469,7 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i
|
||||
ExchangeName: o.GetName(),
|
||||
}
|
||||
|
||||
err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook, o.GetName(), true)
|
||||
err := o.Websocket.Orderbook.LoadSnapshot(&newOrderBook, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -484,43 +484,29 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, i
|
||||
// WsProcessUpdateOrderbook updates an existing orderbook using websocket data
|
||||
// After merging WS data, it will sort, validate and finally update the existing orderbook
|
||||
func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument currency.Pair, tableName string) error {
|
||||
internalOrderbook, err := o.FetchOrderbook(instrument, o.GetAssetTypeFromTableName(tableName))
|
||||
update := wsorderbook.WebsocketOrderbookUpdate{
|
||||
AssetType: asset.Spot,
|
||||
CurrencyPair: instrument,
|
||||
UpdateTime: wsEventData.Timestamp,
|
||||
}
|
||||
update.Asks = o.AppendWsOrderbookItems(wsEventData.Asks)
|
||||
update.Bids = o.AppendWsOrderbookItems(wsEventData.Bids)
|
||||
err := o.Websocket.Orderbook.Update(&update)
|
||||
if err != nil {
|
||||
return errors.New("orderbook nil, could not load existing orderbook")
|
||||
log.Error(log.ExchangeSys, err)
|
||||
}
|
||||
if internalOrderbook.LastUpdated.After(wsEventData.Timestamp) {
|
||||
if o.Verbose {
|
||||
log.Errorf(log.ExchangeSys, "Orderbook update out of order. Existing: %v, Attempted: %v", internalOrderbook.LastUpdated.Unix(), wsEventData.Timestamp.Unix())
|
||||
}
|
||||
return errors.New("updated orderbook is older than existing")
|
||||
}
|
||||
internalOrderbook.Asks = o.WsUpdateOrderbookEntry(wsEventData.Asks, internalOrderbook.Asks)
|
||||
internalOrderbook.Bids = o.WsUpdateOrderbookEntry(wsEventData.Bids, internalOrderbook.Bids)
|
||||
sort.Slice(internalOrderbook.Asks, func(i, j int) bool {
|
||||
return internalOrderbook.Asks[i].Price < internalOrderbook.Asks[j].Price
|
||||
})
|
||||
sort.Slice(internalOrderbook.Bids, func(i, j int) bool {
|
||||
return internalOrderbook.Bids[i].Price > internalOrderbook.Bids[j].Price
|
||||
})
|
||||
checksum := o.CalculateUpdateOrderbookChecksum(&internalOrderbook)
|
||||
updatedOb := o.Websocket.Orderbook.GetOrderbook(instrument, asset.Spot)
|
||||
checksum := o.CalculateUpdateOrderbookChecksum(updatedOb)
|
||||
if checksum == wsEventData.Checksum {
|
||||
if o.Verbose {
|
||||
log.Debug(log.ExchangeSys, "Orderbook valid")
|
||||
}
|
||||
internalOrderbook.LastUpdated = wsEventData.Timestamp
|
||||
if o.Verbose {
|
||||
log.Debug(log.ExchangeSys, "Internalising orderbook")
|
||||
}
|
||||
|
||||
err := o.Websocket.Orderbook.LoadSnapshot(&internalOrderbook, o.GetName(), true)
|
||||
if err != nil {
|
||||
log.Error(log.ExchangeSys, err)
|
||||
}
|
||||
o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: o.GetAssetTypeFromTableName(tableName),
|
||||
Pair: instrument,
|
||||
}
|
||||
|
||||
} else {
|
||||
if o.Verbose {
|
||||
log.Warnln(log.ExchangeSys, "Orderbook invalid")
|
||||
@@ -530,35 +516,6 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, in
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsUpdateOrderbookEntry takes WS bid or ask data and merges it with existing orderbook bid or ask data
|
||||
func (o *OKGroup) WsUpdateOrderbookEntry(wsEntries [][]interface{}, existingOrderbookEntries []orderbook.Item) []orderbook.Item {
|
||||
for j := range wsEntries {
|
||||
wsEntryPrice, _ := strconv.ParseFloat(wsEntries[j][0].(string), 64)
|
||||
wsEntryAmount, _ := strconv.ParseFloat(wsEntries[j][1].(string), 64)
|
||||
matchFound := false
|
||||
for k := 0; k < len(existingOrderbookEntries); k++ {
|
||||
if existingOrderbookEntries[k].Price != wsEntryPrice {
|
||||
continue
|
||||
}
|
||||
matchFound = true
|
||||
if wsEntryAmount == 0 {
|
||||
existingOrderbookEntries = append(existingOrderbookEntries[:k], existingOrderbookEntries[k+1:]...)
|
||||
k--
|
||||
continue
|
||||
}
|
||||
existingOrderbookEntries[k].Amount = wsEntryAmount
|
||||
continue
|
||||
}
|
||||
if !matchFound {
|
||||
existingOrderbookEntries = append(existingOrderbookEntries, orderbook.Item{
|
||||
Amount: wsEntryAmount,
|
||||
Price: wsEntryPrice,
|
||||
})
|
||||
}
|
||||
}
|
||||
return existingOrderbookEntries
|
||||
}
|
||||
|
||||
// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask entries from websocket data
|
||||
// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them
|
||||
// This will also work when there are less than 25 entries (for whatever reason)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -54,6 +54,14 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
o.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -157,7 +158,7 @@ func (p *Poloniex) WsHandleData() {
|
||||
}
|
||||
case "o":
|
||||
currencyPair := currencyIDMap[chanID]
|
||||
err := p.WsProcessOrderbookUpdate(dataL3, currencyPair)
|
||||
err := p.WsProcessOrderbookUpdate(int64(data[1].(float64)), dataL3, currencyPair)
|
||||
if err != nil {
|
||||
p.Websocket.DataHandler <- err
|
||||
continue
|
||||
@@ -328,43 +329,34 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e
|
||||
newOrderBook.Asks = asks
|
||||
newOrderBook.Bids = bids
|
||||
newOrderBook.AssetType = asset.Spot
|
||||
newOrderBook.LastUpdated = time.Now()
|
||||
newOrderBook.Pair = currency.NewPairFromString(symbol)
|
||||
|
||||
return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook, p.GetName(), false)
|
||||
return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook, false)
|
||||
}
|
||||
|
||||
// WsProcessOrderbookUpdate processses new orderbook updates
|
||||
func (p *Poloniex) WsProcessOrderbookUpdate(target []interface{}, symbol string) error {
|
||||
// WsProcessOrderbookUpdate processes new orderbook updates
|
||||
func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []interface{}, symbol string) error {
|
||||
sideCheck := target[1].(float64)
|
||||
|
||||
cP := currency.NewPairFromString(symbol)
|
||||
|
||||
price, err := strconv.ParseFloat(target[2].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
volume, err := strconv.ParseFloat(target[3].(string), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sideCheck == 0 {
|
||||
return p.Websocket.Orderbook.Update(nil,
|
||||
[]orderbook.Item{{Price: price, Amount: volume}},
|
||||
cP,
|
||||
time.Now(),
|
||||
p.GetName(),
|
||||
asset.Spot)
|
||||
update := &wsorderbook.WebsocketOrderbookUpdate{
|
||||
CurrencyPair: cP,
|
||||
AssetType: asset.Spot,
|
||||
UpdateID: sequenceNumber,
|
||||
}
|
||||
|
||||
return p.Websocket.Orderbook.Update([]orderbook.Item{{Price: price, Amount: volume}},
|
||||
nil,
|
||||
cP,
|
||||
time.Now(),
|
||||
p.GetName(),
|
||||
asset.Spot)
|
||||
if sideCheck == 0 {
|
||||
update.Bids = []orderbook.Item{{Price: price, Amount: volume}}
|
||||
} else {
|
||||
update.Asks = []orderbook.Item{{Price: price, Amount: volume}}
|
||||
}
|
||||
return p.Websocket.Orderbook.Update(update)
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/wshandler"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -98,6 +98,7 @@ func (p *Poloniex) SetDefaults() {
|
||||
wshandler.WebsocketAuthenticatedEndpointsSupported
|
||||
p.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
|
||||
p.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
|
||||
p.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
|
||||
}
|
||||
|
||||
// Setup sets user exchange configuration settings
|
||||
@@ -133,6 +134,14 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) error {
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
}
|
||||
|
||||
p.Websocket.Orderbook.Setup(
|
||||
exch.WebsocketOrderbookBufferLimit,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
exch.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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,17 +1,21 @@
|
||||
package wshandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
@@ -97,15 +101,15 @@ func (w *Websocket) Connect() error {
|
||||
go w.trafficMonitor(&anotherWG)
|
||||
anotherWG.Wait()
|
||||
if !w.connectionMonitorRunning {
|
||||
go w.wsConnectionMonitor()
|
||||
go w.connectionMonitor()
|
||||
}
|
||||
go w.manageSubscriptions()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsConnectionMonitor ensures that the WS keeps connecting
|
||||
func (w *Websocket) wsConnectionMonitor() {
|
||||
// connectionMonitor ensures that the WS keeps connecting
|
||||
func (w *Websocket) connectionMonitor() {
|
||||
w.m.Lock()
|
||||
w.connectionMonitorRunning = true
|
||||
w.m.Unlock()
|
||||
@@ -118,13 +122,14 @@ func (w *Websocket) wsConnectionMonitor() {
|
||||
w.m.Lock()
|
||||
if !w.enabled {
|
||||
w.m.Unlock()
|
||||
w.DataHandler <- fmt.Errorf("%v WsConnectionMonitor: websocket disabled, shutting down", w.exchangeName)
|
||||
w.DataHandler <- fmt.Errorf("%v connectionMonitor: websocket disabled, shutting down", w.exchangeName)
|
||||
err := w.Shutdown()
|
||||
if err != nil {
|
||||
log.Error(log.WebsocketMgr, err)
|
||||
}
|
||||
if w.verbose {
|
||||
log.Debugf(log.WebsocketMgr, "%v WsConnectionMonitor exiting", w.exchangeName)
|
||||
log.Debugf(log.WebsocketMgr, "%v connectionMonitor exiting",
|
||||
w.exchangeName)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -200,7 +205,6 @@ func (w *Websocket) Shutdown() error {
|
||||
if !w.connected && w.ShutdownC == nil {
|
||||
return fmt.Errorf("%v cannot shutdown a disconnected websocket", w.exchangeName)
|
||||
}
|
||||
|
||||
if w.verbose {
|
||||
log.Debugf(log.WebsocketMgr, "%v shutting down websocket channels", w.exchangeName)
|
||||
}
|
||||
@@ -227,11 +231,11 @@ func (w *Websocket) Shutdown() error {
|
||||
}
|
||||
|
||||
// WebsocketReset sends the shutdown command, waits for channel/func closure and then reconnects
|
||||
func (w *Websocket) WebsocketReset() error {
|
||||
func (w *Websocket) WebsocketReset() {
|
||||
err := w.Shutdown()
|
||||
if err != nil {
|
||||
// does not return here to allow connection to be made if already shut down
|
||||
log.Errorf(log.WebsocketMgr, "%v shutdown error: %v", w.exchangeName, err)
|
||||
w.DataHandler <- fmt.Errorf("%v shutdown error: %v", w.exchangeName, err)
|
||||
}
|
||||
log.Infof(log.WebsocketMgr, "%v reconnecting to websocket", w.exchangeName)
|
||||
w.m.Lock()
|
||||
@@ -239,9 +243,8 @@ func (w *Websocket) WebsocketReset() error {
|
||||
w.m.Unlock()
|
||||
err = w.Connect()
|
||||
if err != nil {
|
||||
log.Errorf(log.WebsocketMgr, "%v connection error: %v", w.exchangeName, err)
|
||||
w.DataHandler <- fmt.Errorf("%v connection error: %v", w.exchangeName, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// trafficMonitor monitors traffic and switches connection modes for websocket
|
||||
@@ -345,7 +348,6 @@ func (w *Websocket) SetWsStatusAndConnection(enabled bool) error {
|
||||
enabled)
|
||||
}
|
||||
w.enabled = enabled
|
||||
|
||||
if !w.init {
|
||||
if enabled {
|
||||
if w.connected {
|
||||
@@ -421,216 +423,6 @@ func (w *Websocket) GetName() string {
|
||||
return w.exchangeName
|
||||
}
|
||||
|
||||
// Update updates a local cache using bid targets and ask targets then updates
|
||||
// main cache in orderbook.go
|
||||
// Volume == 0; deletion at price target
|
||||
// Price target not found; append of price target
|
||||
// Price target found; amend volume of price target
|
||||
func (w *WebsocketOrderbookLocal) Update(bidTargets, askTargets []orderbook.Item,
|
||||
p currency.Pair,
|
||||
updated time.Time,
|
||||
exchName string, assetType asset.Item) error {
|
||||
if bidTargets == nil && askTargets == nil {
|
||||
return errors.New("exchange.go websocket orderbook cache Update() error - cannot have bids and ask targets both nil")
|
||||
}
|
||||
|
||||
if w.lastUpdated.After(updated) {
|
||||
return errors.New("exchange.go WebsocketOrderbookLocal Update() - update is before last update time")
|
||||
}
|
||||
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
|
||||
var orderbookAddress *orderbook.Base
|
||||
for i := range w.ob {
|
||||
if w.ob[i].Pair == p && w.ob[i].AssetType == assetType {
|
||||
orderbookAddress = w.ob[i]
|
||||
}
|
||||
}
|
||||
|
||||
if orderbookAddress == nil {
|
||||
return fmt.Errorf("exchange.go WebsocketOrderbookLocal Update() - orderbook.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
|
||||
exchName,
|
||||
p.String(),
|
||||
assetType)
|
||||
}
|
||||
|
||||
if len(orderbookAddress.Asks) == 0 || len(orderbookAddress.Bids) == 0 {
|
||||
return errors.New("exchange.go websocket orderbook cache Update() error - snapshot incorrectly loaded")
|
||||
}
|
||||
|
||||
if orderbookAddress.Pair == (currency.Pair{}) {
|
||||
return fmt.Errorf("exchange.go websocket orderbook cache Update() error - snapshot not found %v",
|
||||
p)
|
||||
}
|
||||
|
||||
for x := range bidTargets {
|
||||
// bid targets
|
||||
func() {
|
||||
for y := range orderbookAddress.Bids {
|
||||
if orderbookAddress.Bids[y].Price == bidTargets[x].Price {
|
||||
if bidTargets[x].Amount == 0 {
|
||||
// Delete
|
||||
orderbookAddress.Bids = append(orderbookAddress.Bids[:y],
|
||||
orderbookAddress.Bids[y+1:]...)
|
||||
return
|
||||
}
|
||||
// Amend
|
||||
orderbookAddress.Bids[y].Amount = bidTargets[x].Amount
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if bidTargets[x].Amount == 0 {
|
||||
// Makes sure we dont append things we missed
|
||||
return
|
||||
}
|
||||
|
||||
// Append
|
||||
orderbookAddress.Bids = append(orderbookAddress.Bids, orderbook.Item{
|
||||
Price: bidTargets[x].Price,
|
||||
Amount: bidTargets[x].Amount,
|
||||
})
|
||||
}()
|
||||
// bid targets
|
||||
}
|
||||
|
||||
for x := range askTargets {
|
||||
func() {
|
||||
for y := range orderbookAddress.Asks {
|
||||
if orderbookAddress.Asks[y].Price == askTargets[x].Price {
|
||||
if askTargets[x].Amount == 0 {
|
||||
// Delete
|
||||
orderbookAddress.Asks = append(orderbookAddress.Asks[:y],
|
||||
orderbookAddress.Asks[y+1:]...)
|
||||
return
|
||||
}
|
||||
// Amend
|
||||
orderbookAddress.Asks[y].Amount = askTargets[x].Amount
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if askTargets[x].Amount == 0 {
|
||||
// Makes sure we dont append things we missed
|
||||
return
|
||||
}
|
||||
|
||||
// Append
|
||||
orderbookAddress.Asks = append(orderbookAddress.Asks, orderbook.Item{
|
||||
Price: askTargets[x].Price,
|
||||
Amount: askTargets[x].Amount,
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
return orderbookAddress.Process()
|
||||
|
||||
}
|
||||
|
||||
// LoadSnapshot loads initial snapshot of orderbook data, overite allows full
|
||||
// orderbook to be completely rewritten because the exchange is a doing a full
|
||||
// update not an incremental one
|
||||
func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, exchName string, overwrite bool) error {
|
||||
if len(newOrderbook.Asks) == 0 || len(newOrderbook.Bids) == 0 {
|
||||
return errors.New("exchange.go websocket orderbook cache LoadSnapshot() error - snapshot ask and bids are nil")
|
||||
}
|
||||
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
|
||||
for i := range w.ob {
|
||||
if w.ob[i].Pair.Equal(newOrderbook.Pair) && w.ob[i].AssetType == newOrderbook.AssetType {
|
||||
if overwrite {
|
||||
w.ob[i] = newOrderbook
|
||||
return newOrderbook.Process()
|
||||
}
|
||||
return errors.New("exchange.go websocket orderbook cache LoadSnapshot() error - Snapshot instance already found")
|
||||
}
|
||||
}
|
||||
|
||||
w.ob = append(w.ob, newOrderbook)
|
||||
return newOrderbook.Process()
|
||||
}
|
||||
|
||||
// UpdateUsingID updates orderbooks using specified ID
|
||||
func (w *WebsocketOrderbookLocal) UpdateUsingID(bidTargets, askTargets []orderbook.Item,
|
||||
p currency.Pair,
|
||||
exchName string, assetType asset.Item, action string) error {
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
|
||||
var orderbookAddress *orderbook.Base
|
||||
for i := range w.ob {
|
||||
if w.ob[i].Pair == p && w.ob[i].AssetType == assetType {
|
||||
orderbookAddress = w.ob[i]
|
||||
}
|
||||
}
|
||||
|
||||
if orderbookAddress == nil {
|
||||
return fmt.Errorf("exchange.go WebsocketOrderbookLocal Update() - orderbook.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
|
||||
exchName,
|
||||
assetType,
|
||||
p.String())
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "update":
|
||||
for _, target := range bidTargets {
|
||||
for i := range orderbookAddress.Bids {
|
||||
if orderbookAddress.Bids[i].ID == target.ID {
|
||||
orderbookAddress.Bids[i].Amount = target.Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, target := range askTargets {
|
||||
for i := range orderbookAddress.Asks {
|
||||
if orderbookAddress.Asks[i].ID == target.ID {
|
||||
orderbookAddress.Asks[i].Amount = target.Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "delete":
|
||||
for _, target := range bidTargets {
|
||||
for i := range orderbookAddress.Bids {
|
||||
if orderbookAddress.Bids[i].ID == target.ID {
|
||||
orderbookAddress.Bids = append(orderbookAddress.Bids[:i],
|
||||
orderbookAddress.Bids[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, target := range askTargets {
|
||||
for i := range orderbookAddress.Asks {
|
||||
if orderbookAddress.Asks[i].ID == target.ID {
|
||||
orderbookAddress.Asks = append(orderbookAddress.Asks[:i],
|
||||
orderbookAddress.Asks[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "insert":
|
||||
orderbookAddress.Bids = append(orderbookAddress.Bids, bidTargets...)
|
||||
orderbookAddress.Asks = append(orderbookAddress.Asks, askTargets...)
|
||||
}
|
||||
|
||||
return orderbookAddress.Process()
|
||||
}
|
||||
|
||||
// FlushCache flushes w.ob data to be garbage collected and refreshed when a
|
||||
// connection is lost and reconnected
|
||||
func (w *WebsocketOrderbookLocal) FlushCache() {
|
||||
w.m.Lock()
|
||||
w.ob = nil
|
||||
w.m.Unlock()
|
||||
}
|
||||
|
||||
// GetFunctionality returns a functionality bitmask for the websocket
|
||||
// connection
|
||||
func (w *Websocket) GetFunctionality() uint32 {
|
||||
@@ -723,9 +515,10 @@ func (w *Websocket) SetChannelUnsubscriber(unsubscriber func(channelToUnsubscrib
|
||||
}
|
||||
|
||||
// ManageSubscriptions ensures the subscriptions specified continue to be subscribed to
|
||||
func (w *Websocket) manageSubscriptions() error {
|
||||
func (w *Websocket) manageSubscriptions() {
|
||||
if !w.SupportsFunctionality(WebsocketSubscribeSupported) && !w.SupportsFunctionality(WebsocketUnsubscribeSupported) {
|
||||
return fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName)
|
||||
w.DataHandler <- fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName)
|
||||
return
|
||||
}
|
||||
w.Wg.Add(1)
|
||||
defer func() {
|
||||
@@ -741,7 +534,7 @@ func (w *Websocket) manageSubscriptions() error {
|
||||
if w.verbose {
|
||||
log.Debugf(log.WebsocketMgr, "%v shutdown manageSubscriptions", w.exchangeName)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
default:
|
||||
time.Sleep(manageSubscriptionsDelay)
|
||||
if w.verbose {
|
||||
@@ -910,3 +703,164 @@ func (w *Websocket) CanUseAuthenticatedEndpoints() bool {
|
||||
defer w.subscriptionLock.Unlock()
|
||||
return w.canUseAuthenticatedEndpoints
|
||||
}
|
||||
|
||||
// AddResponseWithID adds data to IDResponses with locks and a nil check
|
||||
func (w *WebsocketConnection) AddResponseWithID(id int64, data []byte) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
if w.IDResponses == nil {
|
||||
w.IDResponses = make(map[int64][]byte)
|
||||
}
|
||||
w.IDResponses[id] = data
|
||||
}
|
||||
|
||||
// Dial sets proxy urls and then connects to the websocket
|
||||
func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header) error {
|
||||
if w.ProxyURL != "" {
|
||||
proxy, err := url.Parse(w.ProxyURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dialer.Proxy = http.ProxyURL(proxy)
|
||||
}
|
||||
|
||||
var err error
|
||||
var conStatus *http.Response
|
||||
w.Connection, conStatus, err = dialer.Dial(w.URL, headers)
|
||||
if err != nil {
|
||||
if conStatus != nil {
|
||||
return fmt.Errorf("%v %v %v Error: %v", w.URL, conStatus, conStatus.StatusCode, err)
|
||||
}
|
||||
return fmt.Errorf("%v Error: %v", w.URL, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessage the one true message request. Sends message to WS
|
||||
func (w *WebsocketConnection) SendMessage(data interface{}) error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
json, err := common.JSONEncode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.Verbose {
|
||||
log.Debugf(log.WebsocketMgr,
|
||||
"%v sending message to websocket %v", w.ExchangeName, string(json))
|
||||
}
|
||||
if w.RateLimit > 0 {
|
||||
time.Sleep(time.Duration(w.RateLimit) * time.Millisecond)
|
||||
}
|
||||
return w.Connection.WriteMessage(websocket.TextMessage, json)
|
||||
}
|
||||
|
||||
// SendMessageReturnResponse will send a WS message to the connection
|
||||
// It will then run a goroutine to await a JSON response
|
||||
// If there is no response it will return an error
|
||||
func (w *WebsocketConnection) SendMessageReturnResponse(id int64, request interface{}) ([]byte, error) {
|
||||
err := w.SendMessage(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go w.WaitForResult(id, &wg)
|
||||
defer func() {
|
||||
delete(w.IDResponses, id)
|
||||
}()
|
||||
wg.Wait()
|
||||
if _, ok := w.IDResponses[id]; !ok {
|
||||
return nil, fmt.Errorf("timeout waiting for response with ID %v", id)
|
||||
}
|
||||
|
||||
return w.IDResponses[id], nil
|
||||
}
|
||||
|
||||
// WaitForResult will keep checking w.IDResponses for a response ID
|
||||
// If the timer expires, it will return without
|
||||
func (w *WebsocketConnection) WaitForResult(id int64, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
timer := time.NewTimer(w.ResponseMaxLimit)
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
return
|
||||
default:
|
||||
w.Lock()
|
||||
for k := range w.IDResponses {
|
||||
if k == id {
|
||||
w.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
time.Sleep(w.ResponseCheckTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadMessage reads messages, can handle text, gzip and binary
|
||||
func (w *WebsocketConnection) ReadMessage() (WebsocketResponse, error) {
|
||||
mType, resp, err := w.Connection.ReadMessage()
|
||||
if err != nil {
|
||||
return WebsocketResponse{}, err
|
||||
}
|
||||
var standardMessage []byte
|
||||
switch mType {
|
||||
case websocket.TextMessage:
|
||||
standardMessage = resp
|
||||
case websocket.BinaryMessage:
|
||||
standardMessage, err = w.parseBinaryResponse(resp)
|
||||
if err != nil {
|
||||
return WebsocketResponse{}, err
|
||||
}
|
||||
}
|
||||
if w.Verbose {
|
||||
log.Debugf(log.WebsocketMgr, "%v Websocket message received: %v",
|
||||
w.ExchangeName,
|
||||
string(standardMessage))
|
||||
}
|
||||
return WebsocketResponse{Raw: standardMessage, Type: mType}, nil
|
||||
}
|
||||
|
||||
// parseBinaryResponse parses a websocket binary response into a usable byte array
|
||||
func (w *WebsocketConnection) parseBinaryResponse(resp []byte) ([]byte, error) {
|
||||
var standardMessage []byte
|
||||
var err error
|
||||
// Detect GZIP
|
||||
if resp[0] == 31 && resp[1] == 139 {
|
||||
b := bytes.NewReader(resp)
|
||||
var gReader *gzip.Reader
|
||||
gReader, err = gzip.NewReader(b)
|
||||
if err != nil {
|
||||
return standardMessage, err
|
||||
}
|
||||
standardMessage, err = ioutil.ReadAll(gReader)
|
||||
if err != nil {
|
||||
return standardMessage, err
|
||||
}
|
||||
err = gReader.Close()
|
||||
if err != nil {
|
||||
return standardMessage, err
|
||||
}
|
||||
} else {
|
||||
reader := flate.NewReader(bytes.NewReader(resp))
|
||||
standardMessage, err = ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return standardMessage, err
|
||||
}
|
||||
err = reader.Close()
|
||||
if err != nil {
|
||||
return standardMessage, err
|
||||
}
|
||||
}
|
||||
return standardMessage, nil
|
||||
}
|
||||
|
||||
// GenerateMessageID Creates a messageID to checkout
|
||||
func (w *WebsocketConnection) GenerateMessageID(useNano bool) int64 {
|
||||
if useNano {
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
return time.Now().Unix()
|
||||
}
|
||||
@@ -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,9 +4,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook"
|
||||
)
|
||||
|
||||
// Websocket functionality list and state consts
|
||||
@@ -95,7 +96,7 @@ type Websocket struct {
|
||||
// ShutdownC is the main shutdown channel which controls all websocket go funcs
|
||||
ShutdownC chan struct{}
|
||||
// Orderbook is a local cache of orderbooks
|
||||
Orderbook WebsocketOrderbookLocal
|
||||
Orderbook wsorderbook.WebsocketOrderbookLocal
|
||||
// Wg defines a wait group for websocket routines for cleanly shutting down
|
||||
// routines
|
||||
Wg sync.WaitGroup
|
||||
@@ -114,14 +115,6 @@ type WebsocketChannelSubscription struct {
|
||||
Params map[string]interface{}
|
||||
}
|
||||
|
||||
// WebsocketOrderbookLocal defines a local cache of orderbooks for amending,
|
||||
// appending and deleting changes and updates the main store in orderbook.go
|
||||
type WebsocketOrderbookLocal struct {
|
||||
ob []*orderbook.Base
|
||||
lastUpdated time.Time
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
// WebsocketResponse defines generalised data from the websocket connection
|
||||
type WebsocketResponse struct {
|
||||
Type int
|
||||
@@ -185,3 +178,20 @@ type WebsocketPositionUpdated struct {
|
||||
AssetType asset.Item
|
||||
Exchange string
|
||||
}
|
||||
|
||||
// WebsocketConnection contains all the data needed to send a message to a WS
|
||||
type WebsocketConnection struct {
|
||||
sync.Mutex
|
||||
Verbose bool
|
||||
RateLimit float64
|
||||
ExchangeName string
|
||||
URL string
|
||||
ProxyURL string
|
||||
Wg sync.WaitGroup
|
||||
Connection *websocket.Conn
|
||||
Shutdown chan struct{}
|
||||
// These are the request IDs and the corresponding response JSON
|
||||
IDResponses map[int64][]byte
|
||||
ResponseCheckTimeout time.Duration
|
||||
ResponseMaxLimit time.Duration
|
||||
}
|
||||
243
exchanges/websocket/wsorderbook/wsorderbook.go
Normal file
243
exchanges/websocket/wsorderbook/wsorderbook.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package wsorderbook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
)
|
||||
|
||||
// Setup sets private variables
|
||||
func (w *WebsocketOrderbookLocal) Setup(obBufferLimit int, bufferEnabled, sortBuffer, sortBufferByUpdateIDs, updateEntriesByID bool, exchangeName string) {
|
||||
w.obBufferLimit = obBufferLimit
|
||||
w.bufferEnabled = bufferEnabled
|
||||
w.sortBuffer = sortBuffer
|
||||
w.sortBufferByUpdateIDs = sortBufferByUpdateIDs
|
||||
w.updateEntriesByID = updateEntriesByID
|
||||
w.exchangeName = exchangeName
|
||||
}
|
||||
|
||||
// Update updates a local cache using bid targets and ask targets then updates
|
||||
// main orderbook
|
||||
// Volume == 0; deletion at price target
|
||||
// Price target not found; append of price target
|
||||
// Price target found; amend volume of price target
|
||||
func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpdate) error {
|
||||
if (orderbookUpdate.Bids == nil && orderbookUpdate.Asks == nil) ||
|
||||
(len(orderbookUpdate.Bids) == 0 && len(orderbookUpdate.Asks) == 0) {
|
||||
return fmt.Errorf("%v cannot have bids and ask targets both nil", w.exchangeName)
|
||||
}
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
if _, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]; !ok {
|
||||
return fmt.Errorf("ob.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s",
|
||||
w.exchangeName,
|
||||
orderbookUpdate.CurrencyPair.String(),
|
||||
orderbookUpdate.AssetType)
|
||||
}
|
||||
if w.bufferEnabled {
|
||||
overBufferLimit := w.processBufferUpdate(orderbookUpdate)
|
||||
if !overBufferLimit {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
w.processObUpdate(orderbookUpdate)
|
||||
}
|
||||
err := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Process()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.bufferEnabled {
|
||||
// Reset the buffer
|
||||
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) processBufferUpdate(orderbookUpdate *WebsocketOrderbookUpdate) bool {
|
||||
if w.buffer == nil {
|
||||
w.buffer = make(map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate)
|
||||
}
|
||||
if w.buffer[orderbookUpdate.CurrencyPair] == nil {
|
||||
w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]WebsocketOrderbookUpdate)
|
||||
}
|
||||
if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) <= w.obBufferLimit {
|
||||
w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = append(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], *orderbookUpdate)
|
||||
if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) < w.obBufferLimit {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if w.sortBuffer {
|
||||
// sort by last updated to ensure each update is in order
|
||||
if w.sortBufferByUpdateIDs {
|
||||
sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool {
|
||||
return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateID < w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateID
|
||||
})
|
||||
} else {
|
||||
sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool {
|
||||
return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateTime.Before(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateTime)
|
||||
})
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]); i++ {
|
||||
w.processObUpdate(&w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i])
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) processObUpdate(orderbookUpdate *WebsocketOrderbookUpdate) {
|
||||
if w.updateEntriesByID {
|
||||
w.updateByIDAndAction(orderbookUpdate)
|
||||
} else {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go w.updateAsksByPrice(orderbookUpdate, &wg)
|
||||
go w.updateBidsByPrice(orderbookUpdate, &wg)
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) updateAsksByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) {
|
||||
for j := 0; j < len(base.Asks); j++ {
|
||||
found := false
|
||||
for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Asks); k++ {
|
||||
if w.ob[base.CurrencyPair][base.AssetType].Asks[k].Price == base.Asks[j].Price {
|
||||
found = true
|
||||
if base.Asks[j].Amount == 0 {
|
||||
w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks[:k],
|
||||
w.ob[base.CurrencyPair][base.AssetType].Asks[k+1:]...)
|
||||
break
|
||||
}
|
||||
w.ob[base.CurrencyPair][base.AssetType].Asks[k].Amount = base.Asks[j].Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks, base.Asks[j])
|
||||
}
|
||||
}
|
||||
sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Asks, func(i, j int) bool {
|
||||
return w.ob[base.CurrencyPair][base.AssetType].Asks[i].Price < w.ob[base.CurrencyPair][base.AssetType].Asks[j].Price
|
||||
})
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func (w *WebsocketOrderbookLocal) updateBidsByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) {
|
||||
for j := 0; j < len(base.Bids); j++ {
|
||||
found := false
|
||||
for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Bids); k++ {
|
||||
if w.ob[base.CurrencyPair][base.AssetType].Bids[k].Price == base.Bids[j].Price {
|
||||
found = true
|
||||
if base.Bids[j].Amount == 0 {
|
||||
w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids[:k],
|
||||
w.ob[base.CurrencyPair][base.AssetType].Bids[k+1:]...)
|
||||
break
|
||||
}
|
||||
w.ob[base.CurrencyPair][base.AssetType].Bids[k].Amount = base.Bids[j].Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids, base.Bids[j])
|
||||
}
|
||||
}
|
||||
sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Bids, func(i, j int) bool {
|
||||
return w.ob[base.CurrencyPair][base.AssetType].Bids[i].Price > w.ob[base.CurrencyPair][base.AssetType].Bids[j].Price
|
||||
})
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// updateByIDAndAction will receive an action to execute against the orderbook
|
||||
// it will then match by IDs instead of price to perform the action
|
||||
func (w *WebsocketOrderbookLocal) updateByIDAndAction(orderbookUpdate *WebsocketOrderbookUpdate) {
|
||||
switch orderbookUpdate.Action {
|
||||
case "update":
|
||||
for _, target := range orderbookUpdate.Bids {
|
||||
for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids {
|
||||
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID {
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].Amount = target.Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, target := range orderbookUpdate.Asks {
|
||||
for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks {
|
||||
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID {
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].Amount = target.Amount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "delete":
|
||||
for _, target := range orderbookUpdate.Bids {
|
||||
for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids); i++ {
|
||||
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID {
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[:i],
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i+1:]...)
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, target := range orderbookUpdate.Asks {
|
||||
for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks); i++ {
|
||||
if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID {
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[:i],
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i+1:]...)
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case "insert":
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids, orderbookUpdate.Bids...)
|
||||
w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks, orderbookUpdate.Asks...)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadSnapshot loads initial snapshot of ob data, overwrite allows full
|
||||
// ob to be completely rewritten because the exchange is a doing a full
|
||||
// update not an incremental one
|
||||
func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base, overwrite bool) error {
|
||||
if len(newOrderbook.Asks) == 0 || len(newOrderbook.Bids) == 0 {
|
||||
return fmt.Errorf("%v snapshot ask and bids are nil", w.exchangeName)
|
||||
}
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
if w.ob == nil {
|
||||
w.ob = make(map[currency.Pair]map[asset.Item]*orderbook.Base)
|
||||
}
|
||||
if w.ob[newOrderbook.Pair] == nil {
|
||||
w.ob[newOrderbook.Pair] = make(map[asset.Item]*orderbook.Base)
|
||||
}
|
||||
if w.ob[newOrderbook.Pair][newOrderbook.AssetType] != nil &&
|
||||
(len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Asks) > 0 ||
|
||||
len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Bids) > 0) {
|
||||
if overwrite {
|
||||
w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook
|
||||
return newOrderbook.Process()
|
||||
}
|
||||
return fmt.Errorf("%v snapshot instance already found", w.exchangeName)
|
||||
}
|
||||
w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook
|
||||
return newOrderbook.Process()
|
||||
}
|
||||
|
||||
// GetOrderbook use sparingly. Modifying anything here will ruin hash calculation and cause problems
|
||||
func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, assetType asset.Item) *orderbook.Base {
|
||||
w.m.Lock()
|
||||
defer w.m.Unlock()
|
||||
return w.ob[p][assetType]
|
||||
}
|
||||
|
||||
// FlushCache flushes w.ob data to be garbage collected and refreshed when a
|
||||
// connection is lost and reconnected
|
||||
func (w *WebsocketOrderbookLocal) FlushCache() {
|
||||
w.m.Lock()
|
||||
w.ob = nil
|
||||
w.buffer = nil
|
||||
w.m.Unlock()
|
||||
}
|
||||
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/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
)
|
||||
|
||||
var itemArray = [][]orderbook.Item{
|
||||
{{Price: 1000, Amount: 1, ID: 1}},
|
||||
{{Price: 2000, Amount: 1, ID: 2}},
|
||||
{{Price: 3000, Amount: 1, ID: 3}},
|
||||
{{Price: 3000, Amount: 2, ID: 4}},
|
||||
{{Price: 4000, Amount: 0, ID: 6}},
|
||||
{{Price: 5000, Amount: 1, ID: 5}},
|
||||
}
|
||||
|
||||
const (
|
||||
exchangeName = "exchangeTest"
|
||||
)
|
||||
|
||||
func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, bids []orderbook.Item, err error) {
|
||||
var snapShot1 orderbook.Base
|
||||
curr = currency.NewPairFromString("BTCUSD")
|
||||
asks = []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 6},
|
||||
}
|
||||
bids = []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 6},
|
||||
}
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
obl = &WebsocketOrderbookLocal{}
|
||||
err = obl.LoadSnapshot(&snapShot1, false)
|
||||
return
|
||||
}
|
||||
|
||||
// BenchmarkBufferPerformance demonstrates buffer more performant than multi process calls
|
||||
func BenchmarkBufferPerformance(b *testing.B) {
|
||||
obl, curr, asks, bids, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.sortBuffer = true
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
randomIndex := rand.Intn(5)
|
||||
update.Asks = itemArray[randomIndex]
|
||||
update.Bids = itemArray[randomIndex]
|
||||
err = obl.Update(update)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBufferSortingPerformance benchmark
|
||||
func BenchmarkBufferSortingPerformance(b *testing.B) {
|
||||
obl, curr, asks, bids, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.sortBuffer = true
|
||||
obl.bufferEnabled = true
|
||||
obl.obBufferLimit = 5
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
randomIndex := rand.Intn(5)
|
||||
update.Asks = itemArray[randomIndex]
|
||||
update.Bids = itemArray[randomIndex]
|
||||
err = obl.Update(update)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkNoBufferPerformance demonstrates orderbook process less performant than buffer
|
||||
func BenchmarkNoBufferPerformance(b *testing.B) {
|
||||
obl, curr, asks, bids, err := createSnapshot()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
update := &WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
randomIndex := rand.Intn(5)
|
||||
update.Asks = itemArray[randomIndex]
|
||||
update.Bids = itemArray[randomIndex]
|
||||
err = obl.Update(update)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHittingTheBuffer logic test
|
||||
func TestHittingTheBuffer(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.bufferEnabled = true
|
||||
obl.obBufferLimit = 5
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
|
||||
t.Log(obl.ob[curr][asset.Spot])
|
||||
t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
|
||||
t.Errorf("expected 3 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestInsertWithIDs logic test
|
||||
func TestInsertWithIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.bufferEnabled = true
|
||||
obl.updateEntriesByID = true
|
||||
obl.obBufferLimit = 5
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Action: "insert",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 6 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 6 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSortIDs logic test
|
||||
func TestSortIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.bufferEnabled = true
|
||||
obl.sortBufferByUpdateIDs = true
|
||||
obl.sortBuffer = true
|
||||
obl.obBufferLimit = 5
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateID: int64(i),
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 3 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 3 {
|
||||
t.Errorf("expected 6 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteWithIDs logic test
|
||||
func TestDeleteWithIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.updateEntriesByID = true
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Action: "delete",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 0 {
|
||||
t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 0 {
|
||||
t.Errorf("expected 0 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateWithIDs logic test
|
||||
func TestUpdateWithIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.updateEntriesByID = true
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
bids := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
Action: "update",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Asks) != 1 {
|
||||
t.Log(obl.ob[curr][asset.Spot])
|
||||
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Asks))
|
||||
}
|
||||
if len(obl.ob[curr][asset.Spot].Bids) != 1 {
|
||||
t.Errorf("expected 1 entries, received: %v", len(obl.ob[curr][asset.Spot].Bids))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOutOfOrderIDs logic test
|
||||
func TestOutOfOrderIDs(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outOFOrderIDs := []int64{2, 1, 5, 3, 4, 6}
|
||||
if itemArray[0][0].Price != 1000 {
|
||||
t.Errorf("expected sorted price to be 3000, received: %v", itemArray[1][0].Price)
|
||||
}
|
||||
obl.exchangeName = exchangeName
|
||||
obl.bufferEnabled = true
|
||||
obl.sortBuffer = true
|
||||
obl.obBufferLimit = 5
|
||||
for i := 0; i < len(itemArray); i++ {
|
||||
asks := itemArray[i]
|
||||
err = obl.Update(&WebsocketOrderbookUpdate{
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateID: outOFOrderIDs[i],
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Index 1 since index 0 is price 7000
|
||||
if obl.ob[curr][asset.Spot].Asks[1].Price != 2000 {
|
||||
t.Errorf("expected sorted price to be 3000, received: %v", obl.ob[curr][asset.Spot].Asks[1].Price)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunUpdateWithoutSnapshot logic test
|
||||
func TestRunUpdateWithoutSnapshot(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
asks := []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 8},
|
||||
}
|
||||
bids := []orderbook.Item{
|
||||
{Price: 5999, Amount: 1, ID: 8},
|
||||
{Price: 4000, Amount: 1, ID: 9},
|
||||
}
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
obl.exchangeName = exchangeName
|
||||
err := obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error running update with no snapshot loaded")
|
||||
}
|
||||
if err.Error() != "ob.Base could not be found for Exchange exchangeTest CurrencyPair: BTCUSD AssetType: spot" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunUpdateWithoutAnyUpdates logic test
|
||||
func TestRunUpdateWithoutAnyUpdates(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
snapShot1.Asks = []orderbook.Item{}
|
||||
snapShot1.Bids = []orderbook.Item{}
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
obl.exchangeName = exchangeName
|
||||
err := obl.Update(&WebsocketOrderbookUpdate{
|
||||
Bids: snapShot1.Asks,
|
||||
Asks: snapShot1.Bids,
|
||||
CurrencyPair: curr,
|
||||
UpdateTime: time.Now(),
|
||||
AssetType: asset.Spot,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error running update with no snapshot loaded")
|
||||
}
|
||||
if err.Error() != fmt.Sprintf("%v cannot have bids and ask targets both nil", exchangeName) {
|
||||
t.Fatal("expected nil asks and bids error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunSnapshotWithNoData logic test
|
||||
func TestRunSnapshotWithNoData(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
snapShot1.Asks = []orderbook.Item{}
|
||||
snapShot1.Bids = []orderbook.Item{}
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
snapShot1.ExchangeName = "test"
|
||||
obl.exchangeName = "test"
|
||||
err := obl.LoadSnapshot(&snapShot1,
|
||||
false)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error loading a snapshot")
|
||||
}
|
||||
if err.Error() != "test snapshot ask and bids are nil" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadSnapshotWithOverride logic test
|
||||
func TestLoadSnapshotWithOverride(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
curr := currency.NewPairFromString("BTCUSD")
|
||||
asks := []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 8},
|
||||
}
|
||||
bids := []orderbook.Item{
|
||||
{Price: 4000, Amount: 1, ID: 9},
|
||||
}
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = curr
|
||||
err := obl.LoadSnapshot(&snapShot1, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = obl.LoadSnapshot(&snapShot1, false)
|
||||
if err == nil {
|
||||
t.Error("expected error: 'snapshot instance already found'")
|
||||
}
|
||||
err = obl.LoadSnapshot(&snapShot1, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInsertWithIDs logic test
|
||||
func TestFlushCache(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obl.ob[curr][asset.Spot] == nil {
|
||||
t.Error("expected ob to have ask entries")
|
||||
}
|
||||
obl.FlushCache()
|
||||
if obl.ob[curr][asset.Spot] != nil {
|
||||
t.Error("expected ob be flushed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestInsertingSnapShots logic test
|
||||
func TestInsertingSnapShots(t *testing.T) {
|
||||
var obl WebsocketOrderbookLocal
|
||||
var snapShot1 orderbook.Base
|
||||
asks := []orderbook.Item{
|
||||
{Price: 6000, Amount: 1, ID: 1},
|
||||
{Price: 6001, Amount: 0.5, ID: 2},
|
||||
{Price: 6002, Amount: 2, ID: 3},
|
||||
{Price: 6003, Amount: 3, ID: 4},
|
||||
{Price: 6004, Amount: 5, ID: 5},
|
||||
{Price: 6005, Amount: 2, ID: 6},
|
||||
{Price: 6006, Amount: 1.5, ID: 7},
|
||||
{Price: 6007, Amount: 0.5, ID: 8},
|
||||
{Price: 6008, Amount: 23, ID: 9},
|
||||
{Price: 6009, Amount: 9, ID: 10},
|
||||
{Price: 6010, Amount: 7, ID: 11},
|
||||
}
|
||||
|
||||
bids := []orderbook.Item{
|
||||
{Price: 5999, Amount: 1, ID: 12},
|
||||
{Price: 5998, Amount: 0.5, ID: 13},
|
||||
{Price: 5997, Amount: 2, ID: 14},
|
||||
{Price: 5996, Amount: 3, ID: 15},
|
||||
{Price: 5995, Amount: 5, ID: 16},
|
||||
{Price: 5994, Amount: 2, ID: 17},
|
||||
{Price: 5993, Amount: 1.5, ID: 18},
|
||||
{Price: 5992, Amount: 0.5, ID: 19},
|
||||
{Price: 5991, Amount: 23, ID: 20},
|
||||
{Price: 5990, Amount: 9, ID: 21},
|
||||
{Price: 5989, Amount: 7, ID: 22},
|
||||
}
|
||||
|
||||
snapShot1.Asks = asks
|
||||
snapShot1.Bids = bids
|
||||
snapShot1.AssetType = asset.Spot
|
||||
snapShot1.Pair = currency.NewPairFromString("BTCUSD")
|
||||
err := obl.LoadSnapshot(&snapShot1, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var snapShot2 orderbook.Base
|
||||
asks = []orderbook.Item{
|
||||
{Price: 51, Amount: 1, ID: 1},
|
||||
{Price: 52, Amount: 0.5, ID: 2},
|
||||
{Price: 53, Amount: 2, ID: 3},
|
||||
{Price: 54, Amount: 3, ID: 4},
|
||||
{Price: 55, Amount: 5, ID: 5},
|
||||
{Price: 56, Amount: 2, ID: 6},
|
||||
{Price: 57, Amount: 1.5, ID: 7},
|
||||
{Price: 58, Amount: 0.5, ID: 8},
|
||||
{Price: 59, Amount: 23, ID: 9},
|
||||
{Price: 50, Amount: 9, ID: 10},
|
||||
{Price: 60, Amount: 7, ID: 11},
|
||||
}
|
||||
|
||||
bids = []orderbook.Item{
|
||||
{Price: 49, Amount: 1, ID: 12},
|
||||
{Price: 48, Amount: 0.5, ID: 13},
|
||||
{Price: 47, Amount: 2, ID: 14},
|
||||
{Price: 46, Amount: 3, ID: 15},
|
||||
{Price: 45, Amount: 5, ID: 16},
|
||||
{Price: 44, Amount: 2, ID: 17},
|
||||
{Price: 43, Amount: 1.5, ID: 18},
|
||||
{Price: 42, Amount: 0.5, ID: 19},
|
||||
{Price: 41, Amount: 23, ID: 20},
|
||||
{Price: 40, Amount: 9, ID: 21},
|
||||
{Price: 39, Amount: 7, ID: 22},
|
||||
}
|
||||
|
||||
snapShot2.Asks = asks
|
||||
snapShot2.Bids = bids
|
||||
snapShot2.AssetType = asset.Spot
|
||||
snapShot2.Pair = currency.NewPairFromString("LTCUSD")
|
||||
err = obl.LoadSnapshot(&snapShot2, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var snapShot3 orderbook.Base
|
||||
asks = []orderbook.Item{
|
||||
{Price: 511, Amount: 1, ID: 1},
|
||||
{Price: 52, Amount: 0.5, ID: 2},
|
||||
{Price: 53, Amount: 2, ID: 3},
|
||||
{Price: 54, Amount: 3, ID: 4},
|
||||
{Price: 55, Amount: 5, ID: 5},
|
||||
{Price: 56, Amount: 2, ID: 6},
|
||||
{Price: 57, Amount: 1.5, ID: 7},
|
||||
{Price: 58, Amount: 0.5, ID: 8},
|
||||
{Price: 59, Amount: 23, ID: 9},
|
||||
{Price: 50, Amount: 9, ID: 10},
|
||||
{Price: 60, Amount: 7, ID: 11},
|
||||
}
|
||||
|
||||
bids = []orderbook.Item{
|
||||
{Price: 49, Amount: 1, ID: 12},
|
||||
{Price: 48, Amount: 0.5, ID: 13},
|
||||
{Price: 47, Amount: 2, ID: 14},
|
||||
{Price: 46, Amount: 3, ID: 15},
|
||||
{Price: 45, Amount: 5, ID: 16},
|
||||
{Price: 44, Amount: 2, ID: 17},
|
||||
{Price: 43, Amount: 1.5, ID: 18},
|
||||
{Price: 42, Amount: 0.5, ID: 19},
|
||||
{Price: 41, Amount: 23, ID: 20},
|
||||
{Price: 40, Amount: 9, ID: 21},
|
||||
{Price: 39, Amount: 7, ID: 22},
|
||||
}
|
||||
|
||||
snapShot3.Asks = asks
|
||||
snapShot3.Bids = bids
|
||||
snapShot3.AssetType = "FUTURES"
|
||||
snapShot3.Pair = currency.NewPairFromString("LTCUSD")
|
||||
err = obl.LoadSnapshot(&snapShot3, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0] != snapShot1.Asks[0] {
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot1.Asks[0], obl.ob[snapShot1.Pair][snapShot1.AssetType].Asks[0])
|
||||
}
|
||||
if obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0] != snapShot2.Asks[0] {
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot2.Asks[0], obl.ob[snapShot2.Pair][snapShot2.AssetType].Asks[0])
|
||||
}
|
||||
if obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0] != snapShot3.Asks[0] {
|
||||
t.Errorf("loaded data mismatch. Expected %v, received %v", snapShot3.Asks[0], obl.ob[snapShot3.Pair][snapShot3.AssetType].Asks[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderbook(t *testing.T) {
|
||||
obl, curr, _, _, err := createSnapshot()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ob := obl.GetOrderbook(curr, asset.Spot)
|
||||
if obl.ob[curr][asset.Spot] != ob {
|
||||
t.Error("Failed to get orderbook")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
w := WebsocketOrderbookLocal{}
|
||||
w.Setup(1, true, true, true, true, "hi")
|
||||
if w.obBufferLimit != 1 || !w.bufferEnabled || !w.sortBuffer || !w.sortBufferByUpdateIDs || !w.updateEntriesByID || w.exchangeName != "hi" {
|
||||
t.Errorf("Setup incorrectly loaded %v", w)
|
||||
}
|
||||
}
|
||||
35
exchanges/websocket/wsorderbook/wsorderbook_types.go
Normal file
35
exchanges/websocket/wsorderbook/wsorderbook_types.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package wsorderbook
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
)
|
||||
|
||||
// WebsocketOrderbookLocal defines a local cache of orderbooks for amending,
|
||||
// appending and deleting changes and updates the main store in wsorderbook.go
|
||||
type WebsocketOrderbookLocal struct {
|
||||
ob map[currency.Pair]map[asset.Item]*orderbook.Base
|
||||
buffer map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate
|
||||
obBufferLimit int
|
||||
bufferEnabled bool
|
||||
sortBuffer bool
|
||||
sortBufferByUpdateIDs bool // When timestamps aren't provided, an id can help sort
|
||||
updateEntriesByID bool // Use the update IDs to match ob entries
|
||||
exchangeName string
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
// WebsocketOrderbookUpdate stores orderbook updates and dictates what features to use when processing
|
||||
type WebsocketOrderbookUpdate struct {
|
||||
UpdateID int64 // Used when no time is provided
|
||||
UpdateTime time.Time
|
||||
AssetType asset.Item
|
||||
Action string // Used in conjunction with UpdateEntriesByID
|
||||
Bids []orderbook.Item
|
||||
Asks []orderbook.Item
|
||||
CurrencyPair currency.Pair
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user