Files
gocryptotrader/exchanges/hitbtc/hitbtc_websocket.go
Andrew d01e7bad72 Implement Logger (#228)
* Added new base logger

* updated example and test configs

* updated exchange helpers restful router & server

* logPath is now passed to the logger to remove dependency on common package

* updated everything besides exchanges to use new logger

* alphapoint to bitmex done

* updated bitmex bitstamp bittrex btcc and also performance changes to logger

* btcmarkets coinbase coinut exmo gateio wrappers updated

* gateio and gemini logger updated

* hitbtc huobi itbit & kraken updated

* All exchanges updatd

* return correct error for disabled websocket

* don't disconnect client on invalid json

* updated router internal logging

* log.Fatal to t.Error for tests

* Changed from fatal to error failure to set maxprocs

* output ANSI codes for everything but windows for now due to lack of windows support

* added error handling to logger and unit tests

* clear wording on print -> log.print

* added benchmark test

* cleaned up import sections

* Updated logger based on PR requests (added default config options on failure/setting errors)

* ah this should fix travici enc config issue

* Load entire config and clear out logging to hopefully fix travisci issue

* wording & test error handling

* fixed formatting issues based on feedback

* fixed formatting issues based on feedback

* changed CheckDir to use mkdirall instead of mkdir and other changes based on feedback
2019-01-08 21:56:22 +11:00

373 lines
8.7 KiB
Go

package hitbtc
import (
"errors"
"fmt"
"net/http"
"net/url"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
hitbtcWebsocketAddress = "wss://api.hitbtc.com/api/2/ws"
rpcVersion = "2.0"
)
// WsConnect starts a new connection with the websocket API
func (h *HitBTC) WsConnect() error {
if !h.Websocket.IsEnabled() || !h.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if h.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(h.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
h.WebsocketConn, _, err = dialer.Dial(hitbtcWebsocketAddress, http.Header{})
if err != nil {
return err
}
go h.WsReadData()
go h.WsHandleData()
err = h.WsSubscribe()
if err != nil {
return err
}
return nil
}
// WsSubscribe subscribes to the relevant channels
func (h *HitBTC) WsSubscribe() error {
enabledPairs := h.GetEnabledCurrencies()
for _, p := range enabledPairs {
pF := exchange.FormatExchangeCurrency(h.GetName(), p)
tickerSubReq, err := common.JSONEncode(WsNotification{
JSONRPCVersion: rpcVersion,
Method: "subscribeTicker",
Params: params{Symbol: pF.String()},
})
if err != nil {
return err
}
err = h.WebsocketConn.WriteMessage(websocket.TextMessage, tickerSubReq)
if err != nil {
return nil
}
orderbookSubReq, err := common.JSONEncode(WsNotification{
JSONRPCVersion: rpcVersion,
Method: "subscribeOrderbook",
Params: params{Symbol: pF.String()},
})
if err != nil {
return err
}
err = h.WebsocketConn.WriteMessage(websocket.TextMessage, orderbookSubReq)
if err != nil {
return nil
}
tradeSubReq, err := common.JSONEncode(WsNotification{
JSONRPCVersion: rpcVersion,
Method: "subscribeTrades",
Params: params{Symbol: pF.String()},
})
if err != nil {
return err
}
err = h.WebsocketConn.WriteMessage(websocket.TextMessage, tradeSubReq)
if err != nil {
return nil
}
}
return nil
}
// WsReadData reads from the websocket connection
func (h *HitBTC) WsReadData() {
h.Websocket.Wg.Add(1)
defer func() {
err := h.WebsocketConn.Close()
if err != nil {
h.Websocket.DataHandler <- fmt.Errorf("hitbtc_websocket.go - Unable to to close Websocket connection. Error: %s",
err)
}
h.Websocket.Wg.Done()
}()
for {
select {
case <-h.Websocket.ShutdownC:
return
default:
_, resp, err := h.WebsocketConn.ReadMessage()
if err != nil {
h.Websocket.DataHandler <- err
return
}
h.Websocket.TrafficAlert <- struct{}{}
h.Websocket.Intercomm <- exchange.WebsocketResponse{Raw: resp}
}
}
}
// WsHandleData handles websocket data
func (h *HitBTC) WsHandleData() {
h.Websocket.Wg.Add(1)
defer h.Websocket.Wg.Done()
for {
select {
case <-h.Websocket.ShutdownC:
case resp := <-h.Websocket.Intercomm:
var init capture
err := common.JSONDecode(resp.Raw, &init)
if err != nil {
log.Error(err)
}
if init.Error.Message != "" || init.Error.Code != 0 {
h.Websocket.DataHandler <- fmt.Errorf("hitbtc.go error - Code: %d, Message: %s",
init.Error.Code,
init.Error.Message)
continue
}
if init.Result {
continue
}
switch init.Method {
case "ticker":
var ticker WsTicker
err := common.JSONDecode(resp.Raw, &ticker)
if err != nil {
log.Error(err)
}
ts, err := time.Parse(time.RFC3339, ticker.Params.Timestamp)
if err != nil {
log.Error(err)
}
h.Websocket.DataHandler <- exchange.TickerData{
Exchange: h.GetName(),
AssetType: "SPOT",
Pair: pair.NewCurrencyPairFromString(ticker.Params.Symbol),
Quantity: ticker.Params.Volume,
Timestamp: ts,
OpenPrice: ticker.Params.Open,
HighPrice: ticker.Params.High,
LowPrice: ticker.Params.Low,
}
case "snapshotOrderbook":
var obSnapshot WsOrderbook
err := common.JSONDecode(resp.Raw, &obSnapshot)
if err != nil {
log.Error(err)
}
err = h.WsProcessOrderbookSnapshot(obSnapshot)
if err != nil {
log.Error(err)
}
case "updateOrderbook":
var obUpdate WsOrderbook
err := common.JSONDecode(resp.Raw, &obUpdate)
if err != nil {
log.Error(err)
}
h.WsProcessOrderbookUpdate(obUpdate)
case "snapshotTrades":
var tradeSnapshot WsTrade
err := common.JSONDecode(resp.Raw, &tradeSnapshot)
if err != nil {
log.Error(err)
}
case "updateTrades":
var tradeUpdates WsTrade
err := common.JSONDecode(resp.Raw, &tradeUpdates)
if err != nil {
log.Error(err)
}
}
}
}
}
// WsProcessOrderbookSnapshot processes a full orderbook snapshot to a local cache
func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error {
if len(ob.Params.Bid) == 0 || len(ob.Params.Ask) == 0 {
return errors.New("hitbtc.go error - no orderbooks to process")
}
var bids []orderbook.Item
for _, bid := range ob.Params.Bid {
bids = append(bids, orderbook.Item{Amount: bid.Size, Price: bid.Price})
}
var asks []orderbook.Item
for _, ask := range ob.Params.Ask {
asks = append(asks, orderbook.Item{Amount: ask.Size, Price: ask.Price})
}
p := pair.NewCurrencyPairFromString(ob.Params.Symbol)
var newOrderbook orderbook.Base
newOrderbook.Asks = asks
newOrderbook.Bids = bids
newOrderbook.AssetType = "SPOT"
newOrderbook.CurrencyPair = ob.Params.Symbol
newOrderbook.LastUpdated = time.Now()
newOrderbook.Pair = p
err := h.Websocket.Orderbook.LoadSnapshot(newOrderbook, h.GetName())
if err != nil {
return err
}
h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
Exchange: h.GetName(),
Asset: "SPOT",
Pair: p,
}
return nil
}
// WsProcessOrderbookUpdate updates a local cache
func (h *HitBTC) WsProcessOrderbookUpdate(ob WsOrderbook) error {
if len(ob.Params.Bid) == 0 && len(ob.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 _, ask := range ob.Params.Ask {
asks = append(asks, orderbook.Item{Price: ask.Price, Amount: ask.Size})
}
p := pair.NewCurrencyPairFromString(ob.Params.Symbol)
err := h.Websocket.Orderbook.Update(bids, asks, p, time.Now(), h.GetName(), "SPOT")
if err != nil {
return err
}
h.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
Exchange: h.GetName(),
Asset: "SPOT",
Pair: p,
}
return nil
}
type capture struct {
Method string `json:"method"`
Result bool `json:"result"`
Error struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
// WsRequest defines a request obj for the JSON-RPC and gets a websocket
// response
type WsRequest struct {
Method string `json:"method"`
Params interface{} `json:"params,omitempty"`
ID interface{} `json:"id"`
}
// WsNotification defines a notification obj for the JSON-RPC this does not get
// a websocket response
type WsNotification struct {
JSONRPCVersion string `json:"jsonrpc"`
Method string `json:"method"`
Params interface{} `json:"params"`
}
type params struct {
Symbol string `json:"symbol"`
}
// WsTicker defines websocket ticker feed return params
type WsTicker struct {
Params struct {
Ask float64 `json:"ask,string"`
Bid float64 `json:"bid,string"`
Last float64 `json:"last,string"`
Open float64 `json:"open,string"`
Low float64 `json:"low,string"`
High float64 `json:"high,string"`
Volume float64 `json:"volume,string"`
VolumeQuote float64 `json:"volumeQuote,string"`
Timestamp string `json:"timestamp"`
Symbol string `json:"symbol"`
} `json:"params"`
}
// WsOrderbook defines websocket orderbook feed return params
type WsOrderbook struct {
Params struct {
Ask []struct {
Price float64 `json:"price,string"`
Size float64 `json:"size,string"`
} `json:"ask"`
Bid []struct {
Price float64 `json:"price,string"`
Size float64 `json:"size,string"`
} `json:"bid"`
Symbol string `json:"symbol"`
Sequence int64 `json:"sequence"`
} `json:"params"`
}
// WsTrade defines websocket trade feed return params
type WsTrade struct {
Params struct {
Data []struct {
ID int64 `json:"id"`
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
Side string `json:"side"`
Timestamp string `json:"timestamp"`
} `json:"data"`
Symbol string `json:"symbol"`
} `json:"params"`
}