Files
gocryptotrader/exchanges/okex/okex_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

335 lines
8.2 KiB
Go

package okex
import (
"bytes"
"compress/flate"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
okexDefaultWebsocketURL = "wss://real.okex.com:10440/websocket/okexapi"
)
func (o *OKEX) writeToWebsocket(message string) error {
o.mu.Lock()
defer o.mu.Unlock()
return o.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(message))
}
// WsConnect initiates a websocket connection
func (o *OKEX) WsConnect() error {
if !o.Websocket.IsEnabled() || !o.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if o.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(o.Websocket.GetProxyAddress())
if err != nil {
return err
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
http.Header{})
if err != nil {
return fmt.Errorf("%s Unable to connect to Websocket. Error: %s",
o.Name,
err)
}
go o.WsHandleData()
go o.WsReadData()
go o.wsPingHandler()
err = o.WsSubscribe()
if err != nil {
return fmt.Errorf("Error: Could not subscribe to the OKEX websocket %s",
err)
}
return nil
}
// WsSubscribe subscribes to the websocket channels
func (o *OKEX) WsSubscribe() error {
myEnabledSubscriptionChannels := []string{}
for _, pair := range o.EnabledPairs {
// ----------- deprecate when usd pairs are upgraded to usdt ----------
checkSymbol := common.SplitStrings(pair, "_")
for i := range checkSymbol {
if common.StringContains(checkSymbol[i], "usdt") {
break
}
if common.StringContains(checkSymbol[i], "usd") {
checkSymbol[i] = "usdt"
}
}
symbolRedone := common.JoinStrings(checkSymbol, "_")
// ----------- deprecate when usd pairs are upgraded to usdt ----------
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels,
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_ticker'}",
symbolRedone))
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels,
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_depth'}",
symbolRedone))
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels,
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_deals'}",
symbolRedone))
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels,
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_kline_1min'}",
symbolRedone))
}
for _, outgoing := range myEnabledSubscriptionChannels {
err := o.writeToWebsocket(outgoing)
if err != nil {
return err
}
}
return nil
}
// WsReadData reads data from the websocket connection
func (o *OKEX) WsReadData() {
o.Websocket.Wg.Add(1)
defer func() {
err := o.WebsocketConn.Close()
if err != nil {
o.Websocket.DataHandler <- fmt.Errorf("okex_websocket.go - Unable to to close Websocket connection. Error: %s",
err)
}
o.Websocket.Wg.Done()
}()
for {
select {
case <-o.Websocket.ShutdownC:
return
default:
mType, resp, err := o.WebsocketConn.ReadMessage()
if err != nil {
o.Websocket.DataHandler <- err
return
}
o.Websocket.TrafficAlert <- struct{}{}
var standardMessage []byte
switch mType {
case websocket.TextMessage:
standardMessage = resp
case websocket.BinaryMessage:
reader := flate.NewReader(bytes.NewReader(resp))
standardMessage, err = ioutil.ReadAll(reader)
reader.Close()
if err != nil {
o.Websocket.DataHandler <- err
return
}
}
o.Websocket.Intercomm <- exchange.WebsocketResponse{Raw: standardMessage}
}
}
}
func (o *OKEX) wsPingHandler() {
o.Websocket.Wg.Add(1)
defer o.Websocket.Wg.Done()
ticker := time.NewTicker(time.Second * 27)
for {
select {
case <-o.Websocket.ShutdownC:
return
case <-ticker.C:
err := o.writeToWebsocket("{'event':'ping'}")
if err != nil {
o.Websocket.DataHandler <- err
return
}
}
}
}
// WsHandleData handles the read data from the websocket connection
func (o *OKEX) WsHandleData() {
o.Websocket.Wg.Add(1)
defer o.Websocket.Wg.Done()
for {
select {
case <-o.Websocket.ShutdownC:
return
case resp := <-o.Websocket.Intercomm:
multiStreamDataArr := []MultiStreamData{}
err := common.JSONDecode(resp.Raw, &multiStreamDataArr)
if err != nil {
if strings.Contains(string(resp.Raw), "pong") {
continue
} else {
log.Error(err)
return
}
}
for _, multiStreamData := range multiStreamDataArr {
var errResponse ErrorResponse
if common.StringContains(string(resp.Raw), "error_msg") {
err = common.JSONDecode(resp.Raw, &errResponse)
if err != nil {
log.Error(err)
}
o.Websocket.DataHandler <- fmt.Errorf("okex.go error - %s resp: %s ",
errResponse.ErrorMsg,
string(resp.Raw))
continue
}
var newPair string
var assetType string
currencyPairSlice := common.SplitStrings(multiStreamData.Channel, "_")
if len(currencyPairSlice) > 5 {
newPair = currencyPairSlice[3] + "_" + currencyPairSlice[4]
assetType = currencyPairSlice[2]
}
if strings.Contains(multiStreamData.Channel, "ticker") {
var ticker TickerStreamData
err = common.JSONDecode(multiStreamData.Data, &ticker)
if err != nil {
log.Errorf("OKEX Ticker Decode Error: %s", err)
return
}
o.Websocket.DataHandler <- exchange.TickerData{
Timestamp: time.Unix(0, int64(ticker.Timestamp)),
Exchange: o.GetName(),
AssetType: assetType,
}
} else if strings.Contains(multiStreamData.Channel, "deals") {
var deals DealsStreamData
err = common.JSONDecode(multiStreamData.Data, &deals)
if err != nil {
log.Errorf("OKEX Deals Decode Error: %s", err)
return
}
for _, trade := range deals {
price, _ := strconv.ParseFloat(trade[1], 64)
amount, _ := strconv.ParseFloat(trade[2], 64)
time, _ := time.Parse(time.RFC3339, trade[3])
o.Websocket.DataHandler <- exchange.TradeData{
Timestamp: time,
Exchange: o.GetName(),
AssetType: assetType,
CurrencyPair: pair.NewCurrencyPairFromString(newPair),
Price: price,
Amount: amount,
EventType: trade[4],
}
}
} else if strings.Contains(multiStreamData.Channel, "kline") {
var klines KlineStreamData
err := common.JSONDecode(multiStreamData.Data, &klines)
if err != nil {
log.Errorf("OKEX Klines Decode Error: %s", err)
return
}
for _, kline := range klines {
ntime, _ := strconv.ParseInt(kline[0], 10, 64)
open, _ := strconv.ParseFloat(kline[1], 64)
high, _ := strconv.ParseFloat(kline[2], 64)
low, _ := strconv.ParseFloat(kline[3], 64)
close, _ := strconv.ParseFloat(kline[4], 64)
volume, _ := strconv.ParseFloat(kline[5], 64)
o.Websocket.DataHandler <- exchange.KlineData{
Timestamp: time.Unix(ntime, 0),
Pair: pair.NewCurrencyPairFromString(newPair),
AssetType: assetType,
Exchange: o.GetName(),
OpenPrice: open,
HighPrice: high,
LowPrice: low,
ClosePrice: close,
Volume: volume,
}
}
} else if strings.Contains(multiStreamData.Channel, "depth") {
var depth DepthStreamData
err := common.JSONDecode(multiStreamData.Data, &depth)
if err != nil {
log.Errorf("OKEX Depth Decode Error: %s", err)
return
}
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
Exchange: o.GetName(),
Asset: assetType,
Pair: pair.NewCurrencyPairFromString(newPair),
}
}
}
}
}
}
// ErrorResponse defines an error response type from the websocket connection
type ErrorResponse struct {
Result bool `json:"result"`
ErrorMsg string `json:"error_msg"`
ErrorCode int64 `json:"error_code"`
}
// Request defines the JSON request structure to the websocket server
type Request struct {
Event string `json:"event"`
Channel string `json:"channel"`
Parameters string `json:"parameters,omitempty"`
}