mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-19 15:10:05 +00:00
* 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
373 lines
8.7 KiB
Go
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"`
|
|
}
|